Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/tcl8.5.2/generic/tclFCmd.c @ 63

Last change on this file since 63 was 25, checked in by landauf, 16 years ago

added tcl to libs

File size: 28.7 KB
Line 
1/*
2 * tclFCmd.c
3 *
4 *      This file implements the generic portion of file manipulation
5 *      subcommands of the "file" command.
6 *
7 * Copyright (c) 1996-1998 Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id: tclFCmd.c,v 1.43 2007/12/13 15:23:17 dgp Exp $
13 */
14
15#include "tclInt.h"
16
17/*
18 * Declarations for local functions defined in this file:
19 */
20
21static int              CopyRenameOneFile(Tcl_Interp *interp,
22                            Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr,
23                            int copyFlag, int force);
24static Tcl_Obj *        FileBasename(Tcl_Interp *interp, Tcl_Obj *pathPtr);
25static int              FileCopyRename(Tcl_Interp *interp,
26                            int objc, Tcl_Obj *CONST objv[], int copyFlag);
27static int              FileForceOption(Tcl_Interp *interp,
28                            int objc, Tcl_Obj *CONST objv[], int *forcePtr);
29
30/*
31 *---------------------------------------------------------------------------
32 *
33 * TclFileRenameCmd
34 *
35 *      This function implements the "rename" subcommand of the "file"
36 *      command. Filename arguments need to be translated to native format
37 *      before being passed to platform-specific code that implements rename
38 *      functionality.
39 *
40 * Results:
41 *      A standard Tcl result.
42 *
43 * Side effects:
44 *      See the user documentation.
45 *
46 *---------------------------------------------------------------------------
47 */
48
49int
50TclFileRenameCmd(
51    Tcl_Interp *interp,         /* Interp for error reporting or recursive
52                                 * calls in the case of a tricky rename. */
53    int objc,                   /* Number of arguments. */
54    Tcl_Obj *CONST objv[])      /* Argument strings passed to Tcl_FileCmd. */
55{
56    return FileCopyRename(interp, objc, objv, 0);
57}
58
59/*
60 *---------------------------------------------------------------------------
61 *
62 * TclFileCopyCmd
63 *
64 *      This function implements the "copy" subcommand of the "file" command.
65 *      Filename arguments need to be translated to native format before being
66 *      passed to platform-specific code that implements copy functionality.
67 *
68 * Results:
69 *      A standard Tcl result.
70 *
71 * Side effects:
72 *      See the user documentation.
73 *
74 *---------------------------------------------------------------------------
75 */
76
77int
78TclFileCopyCmd(
79    Tcl_Interp *interp,         /* Used for error reporting or recursive calls
80                                 * in the case of a tricky copy. */
81    int objc,                   /* Number of arguments. */
82    Tcl_Obj *CONST objv[])      /* Argument strings passed to Tcl_FileCmd. */
83{
84    return FileCopyRename(interp, objc, objv, 1);
85}
86
87/*
88 *---------------------------------------------------------------------------
89 *
90 * FileCopyRename --
91 *
92 *      Performs the work of TclFileRenameCmd and TclFileCopyCmd. See
93 *      comments for those functions.
94 *
95 * Results:
96 *      See above.
97 *
98 * Side effects:
99 *      See above.
100 *
101 *---------------------------------------------------------------------------
102 */
103
104static int
105FileCopyRename(
106    Tcl_Interp *interp,         /* Used for error reporting. */
107    int objc,                   /* Number of arguments. */
108    Tcl_Obj *CONST objv[],      /* Argument strings passed to Tcl_FileCmd. */
109    int copyFlag)               /* If non-zero, copy source(s). Otherwise,
110                                 * rename them. */
111{
112    int i, result, force;
113    Tcl_StatBuf statBuf;
114    Tcl_Obj *target;
115
116    i = FileForceOption(interp, objc - 2, objv + 2, &force);
117    if (i < 0) {
118        return TCL_ERROR;
119    }
120    i += 2;
121    if ((objc - i) < 2) {
122        Tcl_AppendResult(interp, "wrong # args: should be \"",
123                TclGetString(objv[0]), " ", TclGetString(objv[1]),
124                " ?options? source ?source ...? target\"", NULL);
125        return TCL_ERROR;
126    }
127
128    /*
129     * If target doesn't exist or isn't a directory, try the copy/rename.
130     * More than 2 arguments is only valid if the target is an existing
131     * directory.
132     */
133
134    target = objv[objc - 1];
135    if (Tcl_FSConvertToPathType(interp, target) != TCL_OK) {
136        return TCL_ERROR;
137    }
138
139    result = TCL_OK;
140
141    /*
142     * Call Tcl_FSStat() so that if target is a symlink that points to a
143     * directory we will put the sources in that directory instead of
144     * overwriting the symlink.
145     */
146
147    if ((Tcl_FSStat(target, &statBuf) != 0) || !S_ISDIR(statBuf.st_mode)) {
148        if ((objc - i) > 2) {
149            errno = ENOTDIR;
150            Tcl_PosixError(interp);
151            Tcl_AppendResult(interp, "error ",
152                    (copyFlag ? "copying" : "renaming"), ": target \"",
153                    TclGetString(target), "\" is not a directory", NULL);
154            result = TCL_ERROR;
155        } else {
156            /*
157             * Even though already have target == translated(objv[i+1]), pass
158             * the original argument down, so if there's an error, the error
159             * message will reflect the original arguments.
160             */
161
162            result = CopyRenameOneFile(interp, objv[i], objv[i + 1], copyFlag,
163                    force);
164        }
165        return result;
166    }
167
168    /*
169     * Move each source file into target directory. Extract the basename from
170     * each source, and append it to the end of the target path.
171     */
172
173    for ( ; i<objc-1 ; i++) {
174        Tcl_Obj *jargv[2];
175        Tcl_Obj *source, *newFileName;
176        Tcl_Obj *temp;
177
178        source = FileBasename(interp, objv[i]);
179        if (source == NULL) {
180            result = TCL_ERROR;
181            break;
182        }
183        jargv[0] = objv[objc - 1];
184        jargv[1] = source;
185        temp = Tcl_NewListObj(2, jargv);
186        newFileName = Tcl_FSJoinPath(temp, -1);
187        Tcl_IncrRefCount(newFileName);
188        result = CopyRenameOneFile(interp, objv[i], newFileName, copyFlag,
189                force);
190        Tcl_DecrRefCount(newFileName);
191        Tcl_DecrRefCount(temp);
192        Tcl_DecrRefCount(source);
193
194        if (result == TCL_ERROR) {
195            break;
196        }
197    }
198    return result;
199}
200
201/*
202 *---------------------------------------------------------------------------
203 *
204 * TclFileMakeDirsCmd
205 *
206 *      This function implements the "mkdir" subcommand of the "file" command.
207 *      Filename arguments need to be translated to native format before being
208 *      passed to platform-specific code that implements mkdir functionality.
209 *
210 * Results:
211 *      A standard Tcl result.
212 *
213 * Side effects:
214 *      See the user documentation.
215 *
216 *----------------------------------------------------------------------
217 */
218
219int
220TclFileMakeDirsCmd(
221    Tcl_Interp *interp,         /* Used for error reporting. */
222    int objc,                   /* Number of arguments */
223    Tcl_Obj *CONST objv[])      /* Argument strings passed to Tcl_FileCmd. */
224{
225    Tcl_Obj *errfile;
226    int result, i, j, pobjc;
227    Tcl_Obj *split = NULL;
228    Tcl_Obj *target = NULL;
229    Tcl_StatBuf statBuf;
230
231    errfile = NULL;
232
233    result = TCL_OK;
234    for (i = 2; i < objc; i++) {
235        if (Tcl_FSConvertToPathType(interp, objv[i]) != TCL_OK) {
236            result = TCL_ERROR;
237            break;
238        }
239
240        split = Tcl_FSSplitPath(objv[i],&pobjc);
241        Tcl_IncrRefCount(split);
242        if (pobjc == 0) {
243            errno = ENOENT;
244            errfile = objv[i];
245            break;
246        }
247        for (j = 0; j < pobjc; j++) {
248            target = Tcl_FSJoinPath(split, j + 1);
249            Tcl_IncrRefCount(target);
250
251            /*
252             * Call Tcl_FSStat() so that if target is a symlink that points to
253             * a directory we will create subdirectories in that directory.
254             */
255
256            if (Tcl_FSStat(target, &statBuf) == 0) {
257                if (!S_ISDIR(statBuf.st_mode)) {
258                    errno = EEXIST;
259                    errfile = target;
260                    goto done;
261                }
262            } else if (errno != ENOENT) {
263                /*
264                 * If Tcl_FSStat() failed and the error is anything other than
265                 * non-existence of the target, throw the error.
266                 */
267
268                errfile = target;
269                goto done;
270            } else if (Tcl_FSCreateDirectory(target) != TCL_OK) {
271                /*
272                 * Create might have failed because of being in a race
273                 * condition with another process trying to create the same
274                 * subdirectory.
275                 */
276
277                if (errno == EEXIST) {
278                    if ((Tcl_FSStat(target, &statBuf) == 0)
279                            && S_ISDIR(statBuf.st_mode)) {
280                        /*
281                         * It is a directory that wasn't there before, so keep
282                         * going without error.
283                         */
284
285                        Tcl_ResetResult(interp);
286                    } else {
287                        errfile = target;
288                        goto done;
289                    }
290                } else {
291                    errfile = target;
292                    goto done;
293                }
294            }
295
296            /*
297             * Forget about this sub-path.
298             */
299
300            Tcl_DecrRefCount(target);
301            target = NULL;
302        }
303        Tcl_DecrRefCount(split);
304        split = NULL;
305    }
306
307  done:
308    if (errfile != NULL) {
309        Tcl_AppendResult(interp, "can't create directory \"",
310                TclGetString(errfile), "\": ", Tcl_PosixError(interp), NULL);
311        result = TCL_ERROR;
312    }
313    if (split != NULL) {
314        Tcl_DecrRefCount(split);
315    }
316    if (target != NULL) {
317        Tcl_DecrRefCount(target);
318    }
319    return result;
320}
321
322/*
323 *----------------------------------------------------------------------
324 *
325 * TclFileDeleteCmd
326 *
327 *      This function implements the "delete" subcommand of the "file"
328 *      command.
329 *
330 * Results:
331 *      A standard Tcl result.
332 *
333 * Side effects:
334 *      See the user documentation.
335 *
336 *----------------------------------------------------------------------
337 */
338
339int
340TclFileDeleteCmd(
341    Tcl_Interp *interp,         /* Used for error reporting */
342    int objc,                   /* Number of arguments */
343    Tcl_Obj *CONST objv[])      /* Argument strings passed to Tcl_FileCmd. */
344{
345    int i, force, result;
346    Tcl_Obj *errfile;
347    Tcl_Obj *errorBuffer = NULL;
348
349    i = FileForceOption(interp, objc - 2, objv + 2, &force);
350    if (i < 0) {
351        return TCL_ERROR;
352    }
353    i += 2;
354    if ((objc - i) < 1) {
355        Tcl_AppendResult(interp, "wrong # args: should be \"",
356                TclGetString(objv[0]), " ", TclGetString(objv[1]),
357                " ?options? file ?file ...?\"", NULL);
358        return TCL_ERROR;
359    }
360
361    errfile = NULL;
362    result = TCL_OK;
363
364    for ( ; i < objc; i++) {
365        Tcl_StatBuf statBuf;
366
367        errfile = objv[i];
368        if (Tcl_FSConvertToPathType(interp, objv[i]) != TCL_OK) {
369            result = TCL_ERROR;
370            goto done;
371        }
372
373        /*
374         * Call lstat() to get info so can delete symbolic link itself.
375         */
376
377        if (Tcl_FSLstat(objv[i], &statBuf) != 0) {
378            /*
379             * Trying to delete a file that does not exist is not considered
380             * an error, just a no-op
381             */
382
383            if (errno != ENOENT) {
384                result = TCL_ERROR;
385            }
386        } else if (S_ISDIR(statBuf.st_mode)) {
387            /*
388             * We own a reference count on errorBuffer, if it was set as a
389             * result of this call.
390             */
391
392            result = Tcl_FSRemoveDirectory(objv[i], force, &errorBuffer);
393            if (result != TCL_OK) {
394                if ((force == 0) && (errno == EEXIST)) {
395                    Tcl_AppendResult(interp, "error deleting \"",
396                            TclGetString(objv[i]), "\": directory not empty",
397                            NULL);
398                    Tcl_PosixError(interp);
399                    goto done;
400                }
401
402                /*
403                 * If possible, use the untranslated name for the file.
404                 */
405
406                errfile = errorBuffer;
407
408                /*
409                 * FS supposed to check between translated objv and errfile.
410                 */
411
412                if (Tcl_FSEqualPaths(objv[i], errfile)) {
413                    errfile = objv[i];
414                }
415            }
416        } else {
417            result = Tcl_FSDeleteFile(objv[i]);
418        }
419
420        if (result != TCL_OK) {
421            result = TCL_ERROR;
422
423            /*
424             * It is important that we break on error, otherwise we might end
425             * up owning reference counts on numerous errorBuffers.
426             */
427
428            break;
429        }
430    }
431    if (result != TCL_OK) {
432        if (errfile == NULL) {
433            /*
434             * We try to accomodate poor error results from our Tcl_FS calls.
435             */
436
437            Tcl_AppendResult(interp, "error deleting unknown file: ",
438                    Tcl_PosixError(interp), NULL);
439        } else {
440            Tcl_AppendResult(interp, "error deleting \"",
441                    TclGetString(errfile), "\": ", Tcl_PosixError(interp),
442                    NULL);
443        }
444    }
445
446  done:
447    if (errorBuffer != NULL) {
448        Tcl_DecrRefCount(errorBuffer);
449    }
450    return result;
451}
452
453/*
454 *---------------------------------------------------------------------------
455 *
456 * CopyRenameOneFile
457 *
458 *      Copies or renames specified source file or directory hierarchy to the
459 *      specified target.
460 *
461 * Results:
462 *      A standard Tcl result.
463 *
464 * Side effects:
465 *      Target is overwritten if the force flag is set. Attempting to
466 *      copy/rename a file onto a directory or a directory onto a file will
467 *      always result in an error.
468 *
469 *----------------------------------------------------------------------
470 */
471
472static int
473CopyRenameOneFile(
474    Tcl_Interp *interp,         /* Used for error reporting. */
475    Tcl_Obj *source,            /* Pathname of file to copy. May need to be
476                                 * translated. */
477    Tcl_Obj *target,            /* Pathname of file to create/overwrite. May
478                                 * need to be translated. */
479    int copyFlag,               /* If non-zero, copy files. Otherwise, rename
480                                 * them. */
481    int force)                  /* If non-zero, overwrite target file if it
482                                 * exists. Otherwise, error if target already
483                                 * exists. */
484{
485    int result;
486    Tcl_Obj *errfile, *errorBuffer;
487    Tcl_Obj *actualSource=NULL; /* If source is a link, then this is the real
488                                 * file/directory. */
489    Tcl_StatBuf sourceStatBuf, targetStatBuf;
490
491    if (Tcl_FSConvertToPathType(interp, source) != TCL_OK) {
492        return TCL_ERROR;
493    }
494    if (Tcl_FSConvertToPathType(interp, target) != TCL_OK) {
495        return TCL_ERROR;
496    }
497
498    errfile = NULL;
499    errorBuffer = NULL;
500    result = TCL_ERROR;
501
502    /*
503     * We want to copy/rename links and not the files they point to, so we use
504     * lstat(). If target is a link, we also want to replace the link and not
505     * the file it points to, so we also use lstat() on the target.
506     */
507
508    if (Tcl_FSLstat(source, &sourceStatBuf) != 0) {
509        errfile = source;
510        goto done;
511    }
512    if (Tcl_FSLstat(target, &targetStatBuf) != 0) {
513        if (errno != ENOENT) {
514            errfile = target;
515            goto done;
516        }
517    } else {
518        if (force == 0) {
519            errno = EEXIST;
520            errfile = target;
521            goto done;
522        }
523
524        /*
525         * Prevent copying or renaming a file onto itself. Under Windows, stat
526         * always returns 0 for st_ino. However, the Windows-specific code
527         * knows how to deal with copying or renaming a file on top of itself.
528         * It might be a good idea to write a stat that worked.
529         */
530
531        if ((sourceStatBuf.st_ino != 0) && (targetStatBuf.st_ino != 0)) {
532            if ((sourceStatBuf.st_ino == targetStatBuf.st_ino) &&
533                    (sourceStatBuf.st_dev == targetStatBuf.st_dev)) {
534                result = TCL_OK;
535                goto done;
536            }
537        }
538
539        /*
540         * Prevent copying/renaming a file onto a directory and vice-versa.
541         * This is a policy decision based on the fact that existing
542         * implementations of copy and rename on all platforms also prevent
543         * this.
544         */
545
546        if (S_ISDIR(sourceStatBuf.st_mode)
547                && !S_ISDIR(targetStatBuf.st_mode)) {
548            errno = EISDIR;
549            Tcl_AppendResult(interp, "can't overwrite file \"",
550                    TclGetString(target), "\" with directory \"",
551                    TclGetString(source), "\"", NULL);
552            goto done;
553        }
554        if (!S_ISDIR(sourceStatBuf.st_mode)
555                && S_ISDIR(targetStatBuf.st_mode)) {
556            errno = EISDIR;
557            Tcl_AppendResult(interp, "can't overwrite directory \"",
558                    TclGetString(target), "\" with file \"",
559                    TclGetString(source), "\"", NULL);
560            goto done;
561        }
562
563        /*
564         * The destination exists, but appears to be ok to over-write, and
565         * -force is given. We now try to adjust permissions to ensure the
566         * operation succeeds. If we can't adjust permissions, we'll let the
567         * actual copy/rename return an error later.
568         */
569
570        {
571            Tcl_Obj *perm;
572            int index;
573
574            TclNewLiteralStringObj(perm, "u+w");
575            Tcl_IncrRefCount(perm);
576            if (TclFSFileAttrIndex(target, "-permissions", &index) == TCL_OK) {
577                Tcl_FSFileAttrsSet(NULL, index, target, perm);
578            }
579            Tcl_DecrRefCount(perm);
580        }
581    }
582
583    if (copyFlag == 0) {
584        result = Tcl_FSRenameFile(source, target);
585        if (result == TCL_OK) {
586            goto done;
587        }
588
589        if (errno == EINVAL) {
590            Tcl_AppendResult(interp, "error renaming \"",
591                    TclGetString(source), "\" to \"", TclGetString(target),
592                    "\": trying to rename a volume or "
593                    "move a directory into itself", NULL);
594            goto done;
595        } else if (errno != EXDEV) {
596            errfile = target;
597            goto done;
598        }
599
600        /*
601         * The rename failed because the move was across file systems. Fall
602         * through to copy file and then remove original. Note that the
603         * low-level Tcl_FSRenameFileProc in the filesystem is allowed to
604         * implement cross-filesystem moves itself, if it desires.
605         */
606    }
607
608    actualSource = source;
609    Tcl_IncrRefCount(actualSource);
610
611    /*
612     * Activate the following block to copy files instead of links. However
613     * Tcl's semantics currently say we should copy links, so any such change
614     * should be the subject of careful study on the consequences.
615     *
616     * Perhaps there could be an optional flag to 'file copy' to dictate which
617     * approach to use, with the default being _not_ to have this block
618     * active.
619     */
620
621#if 0
622#ifdef S_ISLNK
623    if (copyFlag && S_ISLNK(sourceStatBuf.st_mode)) {
624        /*
625         * We want to copy files not links. Therefore we must follow the link.
626         * There are two purposes to this 'stat' call here. First we want to
627         * know if the linked-file/dir actually exists, and second, in the
628         * block of code which follows, some 20 lines down, we want to check
629         * if the thing is a file or directory.
630         */
631
632        if (Tcl_FSStat(source, &sourceStatBuf) != 0) {
633            /*
634             * Actual file doesn't exist.
635             */
636
637            Tcl_AppendResult(interp, "error copying \"", TclGetString(source),
638                    "\": the target of this link doesn't exist", NULL);
639            goto done;
640        } else {
641            int counter = 0;
642
643            while (1) {
644                Tcl_Obj *path = Tcl_FSLink(actualSource, NULL, 0);
645                if (path == NULL) {
646                    break;
647                }
648
649                /*
650                 * Now we want to check if this is a relative path, and if so,
651                 * to make it absolute.
652                 */
653
654                if (Tcl_FSGetPathType(path) == TCL_PATH_RELATIVE) {
655                    Tcl_Obj *abs = Tcl_FSJoinToPath(actualSource, 1, &path);
656
657                    if (abs == NULL) {
658                        break;
659                    }
660                    Tcl_IncrRefCount(abs);
661                    Tcl_DecrRefCount(path);
662                    path = abs;
663                }
664                Tcl_DecrRefCount(actualSource);
665                actualSource = path;
666                counter++;
667
668                /*
669                 * Arbitrary limit of 20 links to follow.
670                 */
671
672                if (counter > 20) {
673                    /*
674                     * Too many links.
675                     */
676
677                    Tcl_SetErrno(EMLINK);
678                    errfile = source;
679                    goto done;
680                }
681            }
682            /* Now 'actualSource' is the correct file */
683        }
684    }
685#endif /* S_ISLNK */
686#endif
687
688    if (S_ISDIR(sourceStatBuf.st_mode)) {
689        result = Tcl_FSCopyDirectory(actualSource, target, &errorBuffer);
690        if (result != TCL_OK) {
691            if (errno == EXDEV) {
692                /*
693                 * The copy failed because we're trying to do a
694                 * cross-filesystem copy. We do this through our Tcl library.
695                 */
696
697                Tcl_Obj *copyCommand, *cmdObj, *opObj;
698
699                TclNewObj(copyCommand);
700                TclNewLiteralStringObj(cmdObj, "::tcl::CopyDirectory");
701                Tcl_ListObjAppendElement(interp, copyCommand, cmdObj);
702                if (copyFlag) {
703                    TclNewLiteralStringObj(opObj, "copying");
704                } else {
705                    TclNewLiteralStringObj(opObj, "renaming");
706                }
707                Tcl_ListObjAppendElement(interp, copyCommand, opObj);
708                Tcl_ListObjAppendElement(interp, copyCommand, source);
709                Tcl_ListObjAppendElement(interp, copyCommand, target);
710                Tcl_IncrRefCount(copyCommand);
711                result = Tcl_EvalObjEx(interp, copyCommand,
712                        TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT);
713                Tcl_DecrRefCount(copyCommand);
714                if (result != TCL_OK) {
715                    /*
716                     * There was an error in the Tcl-level copy. We will pass
717                     * on the Tcl error message and can ensure this by setting
718                     * errfile to NULL
719                     */
720
721                    errfile = NULL;
722                }
723            } else {
724                errfile = errorBuffer;
725                if (Tcl_FSEqualPaths(errfile, source)) {
726                    errfile = source;
727                } else if (Tcl_FSEqualPaths(errfile, target)) {
728                    errfile = target;
729                }
730            }
731        }
732    } else {
733        result = Tcl_FSCopyFile(actualSource, target);
734        if ((result != TCL_OK) && (errno == EXDEV)) {
735            result = TclCrossFilesystemCopy(interp, source, target);
736        }
737        if (result != TCL_OK) {
738            /*
739             * We could examine 'errno' to double-check if the problem was
740             * with the target, but we checked the source above, so it should
741             * be quite clear
742             */
743
744            errfile = target;
745
746            /*
747             * We now need to reset the result, because the above call, if it
748             * failed, may have put an error message in place. (Ideally we
749             * would prefer not to pass an interpreter in above, but the
750             * channel IO code used by TclCrossFilesystemCopy currently
751             * requires one).
752             */
753
754            Tcl_ResetResult(interp);
755        }
756    }
757    if ((copyFlag == 0) && (result == TCL_OK)) {
758        if (S_ISDIR(sourceStatBuf.st_mode)) {
759            result = Tcl_FSRemoveDirectory(source, 1, &errorBuffer);
760            if (result != TCL_OK) {
761                if (Tcl_FSEqualPaths(errfile, source) == 0) {
762                    errfile = source;
763                }
764            }
765        } else {
766            result = Tcl_FSDeleteFile(source);
767            if (result != TCL_OK) {
768                errfile = source;
769            }
770        }
771        if (result != TCL_OK) {
772            Tcl_AppendResult(interp, "can't unlink \"", TclGetString(errfile),
773                    "\": ", Tcl_PosixError(interp), NULL);
774            errfile = NULL;
775        }
776    }
777
778  done:
779    if (errfile != NULL) {
780        Tcl_AppendResult(interp, "error ", (copyFlag ? "copying" : "renaming"),
781                 " \"", TclGetString(source), NULL);
782        if (errfile != source) {
783            Tcl_AppendResult(interp, "\" to \"", TclGetString(target), NULL);
784            if (errfile != target) {
785                Tcl_AppendResult(interp, "\": \"", TclGetString(errfile),NULL);
786            }
787        }
788        Tcl_AppendResult(interp, "\": ", Tcl_PosixError(interp), NULL);
789    }
790    if (errorBuffer != NULL) {
791        Tcl_DecrRefCount(errorBuffer);
792    }
793    if (actualSource != NULL) {
794        Tcl_DecrRefCount(actualSource);
795    }
796    return result;
797}
798
799/*
800 *---------------------------------------------------------------------------
801 *
802 * FileForceOption --
803 *
804 *      Helps parse command line options for file commands that take the
805 *      "-force" and "--" options.
806 *
807 * Results:
808 *      The return value is how many arguments from argv were consumed by this
809 *      function, or -1 if there was an error parsing the options. If an error
810 *      occurred, an error message is left in the interp's result.
811 *
812 * Side effects:
813 *      None.
814 *
815 *---------------------------------------------------------------------------
816 */
817
818static int
819FileForceOption(
820    Tcl_Interp *interp,         /* Interp, for error return. */
821    int objc,                   /* Number of arguments. */
822    Tcl_Obj *CONST objv[],      /* Argument strings.  First command line
823                                 * option, if it exists, begins at 0. */
824    int *forcePtr)              /* If the "-force" was specified, *forcePtr is
825                                 * filled with 1, otherwise with 0. */
826{
827    int force, i;
828
829    force = 0;
830    for (i = 0; i < objc; i++) {
831        if (TclGetString(objv[i])[0] != '-') {
832            break;
833        }
834        if (strcmp(TclGetString(objv[i]), "-force") == 0) {
835            force = 1;
836        } else if (strcmp(TclGetString(objv[i]), "--") == 0) {
837            i++;
838            break;
839        } else {
840            Tcl_AppendResult(interp, "bad option \"", TclGetString(objv[i]),
841                    "\": should be -force or --", NULL);
842            return -1;
843        }
844    }
845    *forcePtr = force;
846    return i;
847}
848/*
849 *---------------------------------------------------------------------------
850 *
851 * FileBasename --
852 *
853 *      Given a path in either tcl format (with / separators), or in the
854 *      platform-specific format for the current platform, return all the
855 *      characters in the path after the last directory separator. But, if
856 *      path is the root directory, returns no characters.
857 *
858 * Results:
859 *      Returns the string object that represents the basename. If there is an
860 *      error, an error message is left in interp, and NULL is returned.
861 *
862 * Side effects:
863 *      None.
864 *
865 *---------------------------------------------------------------------------
866 */
867
868static Tcl_Obj *
869FileBasename(
870    Tcl_Interp *interp,         /* Interp, for error return. */
871    Tcl_Obj *pathPtr)           /* Path whose basename to extract. */
872{
873    int objc;
874    Tcl_Obj *splitPtr;
875    Tcl_Obj *resultPtr = NULL;
876
877    splitPtr = Tcl_FSSplitPath(pathPtr, &objc);
878    Tcl_IncrRefCount(splitPtr);
879
880    if (objc != 0) {
881        if ((objc == 1) && (*TclGetString(pathPtr) == '~')) {
882            Tcl_DecrRefCount(splitPtr);
883            if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) {
884                return NULL;
885            }
886            splitPtr = Tcl_FSSplitPath(pathPtr, &objc);
887            Tcl_IncrRefCount(splitPtr);
888        }
889
890        /*
891         * Return the last component, unless it is the only component, and it
892         * is the root of an absolute path.
893         */
894
895        if (objc > 0) {
896            Tcl_ListObjIndex(NULL, splitPtr, objc-1, &resultPtr);
897            if ((objc == 1) &&
898                    (Tcl_FSGetPathType(resultPtr) != TCL_PATH_RELATIVE)) {
899                resultPtr = NULL;
900            }
901        }
902    }
903    if (resultPtr == NULL) {
904        resultPtr = Tcl_NewObj();
905    }
906    Tcl_IncrRefCount(resultPtr);
907    Tcl_DecrRefCount(splitPtr);
908    return resultPtr;
909}
910
911/*
912 *----------------------------------------------------------------------
913 *
914 * TclFileAttrsCmd --
915 *
916 *      Sets or gets the platform-specific attributes of a file. The objc-objv
917 *      points to the file name with the rest of the command line following.
918 *      This routine uses platform-specific tables of option strings and
919 *      callbacks. The callback to get the attributes take three parameters:
920 *          Tcl_Interp *interp;     The interp to report errors with. Since
921 *                                  this is an object-based API, the object
922 *                                  form of the result should be used.
923 *          CONST char *fileName;   This is extracted using
924 *                                  Tcl_TranslateFileName.
925 *          TclObj **attrObjPtrPtr; A new object to hold the attribute is
926 *                                  allocated and put here.
927 *      The first two parameters of the callback used to write out the
928 *      attributes are the same. The third parameter is:
929 *          CONST *attrObjPtr;      A pointer to the object that has the new
930 *                                  attribute.
931 *      They both return standard TCL errors; if the routine to get an
932 *      attribute fails, no object is allocated and *attrObjPtrPtr is
933 *      unchanged.
934 *
935 * Results:
936 *      Standard TCL error.
937 *
938 * Side effects:
939 *      May set file attributes for the file name.
940 *
941 *----------------------------------------------------------------------
942 */
943
944int
945TclFileAttrsCmd(
946    Tcl_Interp *interp,         /* The interpreter for error reporting. */
947    int objc,                   /* Number of command line arguments. */
948    Tcl_Obj *CONST objv[])      /* The command line objects. */
949{
950    int result;
951    CONST char ** attributeStrings;
952    Tcl_Obj* objStrings = NULL;
953    int numObjStrings = -1;
954    Tcl_Obj *filePtr;
955
956    if (objc < 3) {
957        Tcl_WrongNumArgs(interp, 2, objv,
958                "name ?option? ?value? ?option value ...?");
959        return TCL_ERROR;
960    }
961
962    filePtr = objv[2];
963    if (Tcl_FSConvertToPathType(interp, filePtr) != TCL_OK) {
964        return TCL_ERROR;
965    }
966
967    objc -= 3;
968    objv += 3;
969    result = TCL_ERROR;
970    Tcl_SetErrno(0);
971
972    attributeStrings = Tcl_FSFileAttrStrings(filePtr, &objStrings);
973    if (attributeStrings == NULL) {
974        int index;
975        Tcl_Obj *objPtr;
976
977        if (objStrings == NULL) {
978            if (Tcl_GetErrno() != 0) {
979                /*
980                 * There was an error, probably that the filePtr is not
981                 * accepted by any filesystem
982                 */
983                Tcl_AppendResult(interp, "could not read \"",
984                        TclGetString(filePtr), "\": ", Tcl_PosixError(interp),
985                        NULL);
986                return TCL_ERROR;
987            }
988            goto end;
989        }
990
991        /*
992         * We own the object now.
993         */
994
995        Tcl_IncrRefCount(objStrings);
996
997        /*
998         * Use objStrings as a list object.
999         */
1000
1001        if (Tcl_ListObjLength(interp, objStrings, &numObjStrings) != TCL_OK) {
1002            goto end;
1003        }
1004        attributeStrings = (CONST char **) TclStackAlloc(interp,
1005                (1+numObjStrings) * sizeof(char*));
1006        for (index = 0; index < numObjStrings; index++) {
1007            Tcl_ListObjIndex(interp, objStrings, index, &objPtr);
1008            attributeStrings[index] = TclGetString(objPtr);
1009        }
1010        attributeStrings[index] = NULL;
1011    }
1012    if (objc == 0) {
1013        /*
1014         * Get all attributes.
1015         */
1016
1017        int index, res = TCL_OK, nbAtts = 0;
1018        Tcl_Obj *listPtr;
1019
1020        listPtr = Tcl_NewListObj(0, NULL);
1021        for (index = 0; attributeStrings[index] != NULL; index++) {
1022            Tcl_Obj *objPtrAttr;
1023
1024            if (res != TCL_OK) {
1025                /*
1026                 * Clear the error from the last iteration.
1027                 */
1028
1029                Tcl_ResetResult(interp);
1030            }
1031
1032            res = Tcl_FSFileAttrsGet(interp, index, filePtr, &objPtrAttr);
1033            if (res == TCL_OK) {
1034                Tcl_Obj *objPtr =
1035                        Tcl_NewStringObj(attributeStrings[index], -1);
1036
1037                Tcl_ListObjAppendElement(interp, listPtr, objPtr);
1038                Tcl_ListObjAppendElement(interp, listPtr, objPtrAttr);
1039                nbAtts++;
1040            }
1041        }
1042
1043        if (index > 0 && nbAtts == 0) {
1044            /*
1045             * Error: no valid attributes found.
1046             */
1047
1048            Tcl_DecrRefCount(listPtr);
1049            goto end;
1050        }
1051
1052        Tcl_SetObjResult(interp, listPtr);
1053    } else if (objc == 1) {
1054        /*
1055         * Get one attribute.
1056         */
1057
1058        int index;
1059        Tcl_Obj *objPtr = NULL;
1060
1061        if (numObjStrings == 0) {
1062            Tcl_AppendResult(interp, "bad option \"", TclGetString(objv[0]),
1063                    "\", there are no file attributes in this filesystem.",
1064                    NULL);
1065            goto end;
1066        }
1067
1068        if (Tcl_GetIndexFromObj(interp, objv[0], attributeStrings,
1069                "option", 0, &index) != TCL_OK) {
1070            goto end;
1071        }
1072        if (Tcl_FSFileAttrsGet(interp, index, filePtr,
1073                &objPtr) != TCL_OK) {
1074            goto end;
1075        }
1076        Tcl_SetObjResult(interp, objPtr);
1077    } else {
1078        /*
1079         * Set option/value pairs.
1080         */
1081
1082        int i, index;
1083
1084        if (numObjStrings == 0) {
1085            Tcl_AppendResult(interp, "bad option \"", TclGetString(objv[0]),
1086                    "\", there are no file attributes in this filesystem.",
1087                    NULL);
1088            goto end;
1089        }
1090
1091        for (i = 0; i < objc ; i += 2) {
1092            if (Tcl_GetIndexFromObj(interp, objv[i], attributeStrings,
1093                    "option", 0, &index) != TCL_OK) {
1094                goto end;
1095            }
1096            if (i + 1 == objc) {
1097                Tcl_AppendResult(interp, "value for \"",
1098                        TclGetString(objv[i]), "\" missing", NULL);
1099                goto end;
1100            }
1101            if (Tcl_FSFileAttrsSet(interp, index, filePtr,
1102                    objv[i + 1]) != TCL_OK) {
1103                goto end;
1104            }
1105        }
1106    }
1107    result = TCL_OK;
1108
1109  end:
1110    if (numObjStrings != -1) {
1111        /*
1112         * Free up the array we allocated.
1113         */
1114
1115        TclStackFree(interp, (void *)attributeStrings);
1116
1117        /*
1118         * We don't need this object that was passed to us any more.
1119         */
1120
1121        if (objStrings != NULL) {
1122            Tcl_DecrRefCount(objStrings);
1123        }
1124    }
1125    return result;
1126}
1127
1128/*
1129 * Local Variables:
1130 * mode: c
1131 * c-basic-offset: 4
1132 * fill-column: 78
1133 * End:
1134 */
Note: See TracBrowser for help on using the repository browser.