diff options
Diffstat (limited to 'generic/tclFCmd.c')
-rw-r--r-- | generic/tclFCmd.c | 1242 |
1 files changed, 848 insertions, 394 deletions
diff --git a/generic/tclFCmd.c b/generic/tclFCmd.c index 3d6a169..6452fff 100644 --- a/generic/tclFCmd.c +++ b/generic/tclFCmd.c @@ -1,47 +1,40 @@ /* * tclFCmd.c * - * This file implements the generic portion of file manipulation - * subcommands of the "file" command. + * This file implements the generic portion of file manipulation + * subcommands of the "file" command. * * Copyright (c) 1996-1998 Sun Microsystems, Inc. * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ -#ifndef _WIN64 -/* See [Bug 2935503]: file mtime sets wrong time */ -# define _USE_32BIT_TIME_T -#endif - -#include <sys/stat.h> #include "tclInt.h" -#include "tclPort.h" +#include "tclFileSystem.h" /* - * Declarations for local procedures defined in this file: + * Declarations for local functions defined in this file: */ -static int CopyRenameOneFile _ANSI_ARGS_((Tcl_Interp *interp, - Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, - int copyFlag, int force)); -static Tcl_Obj * FileBasename _ANSI_ARGS_((Tcl_Interp *interp, - Tcl_Obj *pathPtr)); -static int FileCopyRename _ANSI_ARGS_((Tcl_Interp *interp, - int objc, Tcl_Obj *CONST objv[], int copyFlag)); -static int FileForceOption _ANSI_ARGS_((Tcl_Interp *interp, - int objc, Tcl_Obj *CONST objv[], int *forcePtr)); +static int CopyRenameOneFile(Tcl_Interp *interp, + Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, + int copyFlag, int force); +static Tcl_Obj * FileBasename(Tcl_Interp *interp, Tcl_Obj *pathPtr); +static int FileCopyRename(Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[], int copyFlag); +static int FileForceOption(Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[], int *forcePtr); /* *--------------------------------------------------------------------------- * * TclFileRenameCmd * - * This procedure implements the "rename" subcommand of the "file" - * command. Filename arguments need to be translated to native - * format before being passed to platform-specific code that - * implements rename functionality. + * This function implements the "rename" subcommand of the "file" + * command. Filename arguments need to be translated to native format + * before being passed to platform-specific code that implements rename + * functionality. * * Results: * A standard Tcl result. @@ -53,10 +46,12 @@ static int FileForceOption _ANSI_ARGS_((Tcl_Interp *interp, */ int -TclFileRenameCmd(interp, objc, objv) - Tcl_Interp *interp; /* Interp for error reporting. */ - int objc; /* Number of arguments. */ - Tcl_Obj *CONST objv[]; /* Argument strings passed to Tcl_FileCmd. */ +TclFileRenameCmd( + ClientData clientData, /* Unused */ + Tcl_Interp *interp, /* Interp for error reporting or recursive + * calls in the case of a tricky rename. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument strings passed to Tcl_FileCmd. */ { return FileCopyRename(interp, objc, objv, 0); } @@ -66,10 +61,9 @@ TclFileRenameCmd(interp, objc, objv) * * TclFileCopyCmd * - * This procedure implements the "copy" subcommand of the "file" - * command. Filename arguments need to be translated to native - * format before being passed to platform-specific code that - * implements copy functionality. + * This function implements the "copy" subcommand of the "file" command. + * Filename arguments need to be translated to native format before being + * passed to platform-specific code that implements copy functionality. * * Results: * A standard Tcl result. @@ -81,10 +75,12 @@ TclFileRenameCmd(interp, objc, objv) */ int -TclFileCopyCmd(interp, objc, objv) - Tcl_Interp *interp; /* Used for error reporting */ - int objc; /* Number of arguments. */ - Tcl_Obj *CONST objv[]; /* Argument strings passed to Tcl_FileCmd. */ +TclFileCopyCmd( + ClientData clientData, /* Unused */ + Tcl_Interp *interp, /* Used for error reporting or recursive calls + * in the case of a tricky copy. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument strings passed to Tcl_FileCmd. */ { return FileCopyRename(interp, objc, objv, 1); } @@ -94,8 +90,8 @@ TclFileCopyCmd(interp, objc, objv) * * FileCopyRename -- * - * Performs the work of TclFileRenameCmd and TclFileCopyCmd. - * See comments for those procedures. + * Performs the work of TclFileRenameCmd and TclFileCopyCmd. See + * comments for those functions. * * Results: * See above. @@ -107,34 +103,31 @@ TclFileCopyCmd(interp, objc, objv) */ static int -FileCopyRename(interp, objc, objv, copyFlag) - Tcl_Interp *interp; /* Used for error reporting. */ - int objc; /* Number of arguments. */ - Tcl_Obj *CONST objv[]; /* Argument strings passed to Tcl_FileCmd. */ - int copyFlag; /* If non-zero, copy source(s). Otherwise, +FileCopyRename( + Tcl_Interp *interp, /* Used for error reporting. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[], /* Argument strings passed to Tcl_FileCmd. */ + int copyFlag) /* If non-zero, copy source(s). Otherwise, * rename them. */ { int i, result, force; - Tcl_StatBuf statBuf; + Tcl_StatBuf statBuf; Tcl_Obj *target; - i = FileForceOption(interp, objc - 2, objv + 2, &force); + i = FileForceOption(interp, objc - 1, objv + 1, &force); if (i < 0) { return TCL_ERROR; } - i += 2; + i++; if ((objc - i) < 2) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - Tcl_GetString(objv[0]), " ", Tcl_GetString(objv[1]), - " ?options? source ?source ...? target\"", - (char *) NULL); + Tcl_WrongNumArgs(interp, 1, objv, + "?-option value ...? source ?source ...? target"); return TCL_ERROR; } /* - * If target doesn't exist or isn't a directory, try the copy/rename. - * More than 2 arguments is only valid if the target is an existing - * directory. + * If target doesn't exist or isn't a directory, try the copy/rename. More + * than 2 arguments is only valid if the target is an existing directory. */ target = objv[objc - 1]; @@ -154,16 +147,15 @@ FileCopyRename(interp, objc, objv, copyFlag) if ((objc - i) > 2) { errno = ENOTDIR; Tcl_PosixError(interp); - Tcl_AppendResult(interp, "error ", - ((copyFlag) ? "copying" : "renaming"), ": target \"", - Tcl_GetString(target), "\" is not a directory", - (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "error %s: target \"%s\" is not a directory", + (copyFlag?"copying":"renaming"), TclGetString(target))); result = TCL_ERROR; } else { /* - * Even though already have target == translated(objv[i+1]), - * pass the original argument down, so if there's an error, the - * error message will reflect the original arguments. + * Even though already have target == translated(objv[i+1]), pass + * the original argument down, so if there's an error, the error + * message will reflect the original arguments. */ result = CopyRenameOneFile(interp, objv[i], objv[i + 1], copyFlag, @@ -171,17 +163,16 @@ FileCopyRename(interp, objc, objv, copyFlag) } return result; } - + /* - * Move each source file into target directory. Extract the basename - * from each source, and append it to the end of the target path. + * Move each source file into target directory. Extract the basename from + * each source, and append it to the end of the target path. */ - for ( ; i < objc - 1; i++) { + for ( ; i<objc-1 ; i++) { Tcl_Obj *jargv[2]; Tcl_Obj *source, *newFileName; - Tcl_Obj *temp; - + source = FileBasename(interp, objv[i]); if (source == NULL) { result = TCL_ERROR; @@ -189,13 +180,11 @@ FileCopyRename(interp, objc, objv, copyFlag) } jargv[0] = objv[objc - 1]; jargv[1] = source; - temp = Tcl_NewListObj(2, jargv); - newFileName = Tcl_FSJoinPath(temp, -1); + newFileName = TclJoinPath(2, jargv); Tcl_IncrRefCount(newFileName); result = CopyRenameOneFile(interp, objv[i], newFileName, copyFlag, force); Tcl_DecrRefCount(newFileName); - Tcl_DecrRefCount(temp); Tcl_DecrRefCount(source); if (result == TCL_ERROR) { @@ -210,10 +199,9 @@ FileCopyRename(interp, objc, objv, copyFlag) * * TclFileMakeDirsCmd * - * This procedure implements the "mkdir" subcommand of the "file" - * command. Filename arguments need to be translated to native - * format before being passed to platform-specific code that - * implements mkdir functionality. + * This function implements the "mkdir" subcommand of the "file" command. + * Filename arguments need to be translated to native format before being + * passed to platform-specific code that implements mkdir functionality. * * Results: * A standard Tcl result. @@ -223,28 +211,29 @@ FileCopyRename(interp, objc, objv, copyFlag) * *---------------------------------------------------------------------- */ + int -TclFileMakeDirsCmd(interp, objc, objv) - Tcl_Interp *interp; /* Used for error reporting. */ - int objc; /* Number of arguments */ - Tcl_Obj *CONST objv[]; /* Argument strings passed to Tcl_FileCmd. */ +TclFileMakeDirsCmd( + ClientData clientData, /* Unused */ + Tcl_Interp *interp, /* Used for error reporting. */ + int objc, /* Number of arguments */ + Tcl_Obj *const objv[]) /* Argument strings passed to Tcl_FileCmd. */ { - Tcl_Obj *errfile; + Tcl_Obj *errfile = NULL; int result, i, j, pobjc; Tcl_Obj *split = NULL; Tcl_Obj *target = NULL; Tcl_StatBuf statBuf; - errfile = NULL; - result = TCL_OK; - for (i = 2; i < objc; i++) { + for (i = 1; i < objc; i++) { if (Tcl_FSConvertToPathType(interp, objv[i]) != TCL_OK) { result = TCL_ERROR; break; } - split = Tcl_FSSplitPath(objv[i],&pobjc); + split = Tcl_FSSplitPath(objv[i], &pobjc); + Tcl_IncrRefCount(split); if (pobjc == 0) { errno = ENOENT; errfile = objv[i]; @@ -253,10 +242,10 @@ TclFileMakeDirsCmd(interp, objc, objv) for (j = 0; j < pobjc; j++) { target = Tcl_FSJoinPath(split, j + 1); Tcl_IncrRefCount(target); + /* - * Call Tcl_FSStat() so that if target is a symlink that - * points to a directory we will create subdirectories in - * that directory. + * Call Tcl_FSStat() so that if target is a symlink that points to + * a directory we will create subdirectories in that directory. */ if (Tcl_FSStat(target, &statBuf) == 0) { @@ -267,36 +256,40 @@ TclFileMakeDirsCmd(interp, objc, objv) } } else if (errno != ENOENT) { /* - * If Tcl_FSStat() failed and the error is anything - * other than non-existence of the target, throw the - * error. + * If Tcl_FSStat() failed and the error is anything other than + * non-existence of the target, throw the error. */ + errfile = target; goto done; } else if (Tcl_FSCreateDirectory(target) != TCL_OK) { /* * Create might have failed because of being in a race - * condition with another process trying to create the - * same subdirectory. + * condition with another process trying to create the same + * subdirectory. */ - if (errno == EEXIST) { - if ((Tcl_FSStat(target, &statBuf) == 0) - && S_ISDIR(statBuf.st_mode)) { - /* - * It is a directory that wasn't there before, - * so keep going without error. - */ - Tcl_ResetResult(interp); - } else { - errfile = target; - goto done; - } + + if (errno != EEXIST) { + errfile = target; + goto done; + } else if ((Tcl_FSStat(target, &statBuf) == 0) + && S_ISDIR(statBuf.st_mode)) { + /* + * It is a directory that wasn't there before, so keep + * going without error. + */ + + Tcl_ResetResult(interp); } else { errfile = target; goto done; } } - /* Forget about this sub-path */ + + /* + * Forget about this sub-path. + */ + Tcl_DecrRefCount(target); target = NULL; } @@ -304,11 +297,11 @@ TclFileMakeDirsCmd(interp, objc, objv) split = NULL; } - done: + done: if (errfile != NULL) { - Tcl_AppendResult(interp, "can't create directory \"", - Tcl_GetString(errfile), "\": ", Tcl_PosixError(interp), - (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't create directory \"%s\": %s", + TclGetString(errfile), Tcl_PosixError(interp))); result = TCL_ERROR; } if (split != NULL) { @@ -325,8 +318,8 @@ TclFileMakeDirsCmd(interp, objc, objv) * * TclFileDeleteCmd * - * This procedure implements the "delete" subcommand of the "file" - * command. + * This function implements the "delete" subcommand of the "file" + * command. * * Results: * A standard Tcl result. @@ -338,31 +331,25 @@ TclFileMakeDirsCmd(interp, objc, objv) */ int -TclFileDeleteCmd(interp, objc, objv) - Tcl_Interp *interp; /* Used for error reporting */ - int objc; /* Number of arguments */ - Tcl_Obj *CONST objv[]; /* Argument strings passed to Tcl_FileCmd. */ +TclFileDeleteCmd( + ClientData clientData, /* Unused */ + Tcl_Interp *interp, /* Used for error reporting */ + int objc, /* Number of arguments */ + Tcl_Obj *const objv[]) /* Argument strings passed to Tcl_FileCmd. */ { int i, force, result; Tcl_Obj *errfile; Tcl_Obj *errorBuffer = NULL; - - i = FileForceOption(interp, objc - 2, objv + 2, &force); + + i = FileForceOption(interp, objc - 1, objv + 1, &force); if (i < 0) { return TCL_ERROR; } - i += 2; - if ((objc - i) < 1) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - Tcl_GetString(objv[0]), " ", Tcl_GetString(objv[1]), - " ?options? file ?file ...?\"", (char *) NULL); - return TCL_ERROR; - } errfile = NULL; result = TCL_OK; - for ( ; i < objc; i++) { + for (i++ ; i < objc; i++) { Tcl_StatBuf statBuf; errfile = objv[i]; @@ -377,34 +364,39 @@ TclFileDeleteCmd(interp, objc, objv) if (Tcl_FSLstat(objv[i], &statBuf) != 0) { /* - * Trying to delete a file that does not exist is not - * considered an error, just a no-op + * Trying to delete a file that does not exist is not considered + * an error, just a no-op */ if (errno != ENOENT) { result = TCL_ERROR; } } else if (S_ISDIR(statBuf.st_mode)) { - /* - * We own a reference count on errorBuffer, if it was set - * as a result of this call. + /* + * We own a reference count on errorBuffer, if it was set as a + * result of this call. */ + result = Tcl_FSRemoveDirectory(objv[i], force, &errorBuffer); if (result != TCL_OK) { if ((force == 0) && (errno == EEXIST)) { - Tcl_AppendResult(interp, "error deleting \"", - Tcl_GetString(objv[i]), - "\": directory not empty", (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "error deleting \"%s\": directory not empty", + TclGetString(objv[i]))); Tcl_PosixError(interp); goto done; } - /* + /* * If possible, use the untranslated name for the file. */ - + errfile = errorBuffer; - /* FS supposed to check between translated objv and errfile */ + + /* + * FS supposed to check between translated objv and errfile. + */ + if (Tcl_FSEqualPaths(objv[i], errfile)) { errfile = objv[i]; } @@ -412,32 +404,35 @@ TclFileDeleteCmd(interp, objc, objv) } else { result = Tcl_FSDeleteFile(objv[i]); } - + if (result != TCL_OK) { result = TCL_ERROR; - /* - * It is important that we break on error, otherwise we - * might end up owning reference counts on numerous - * errorBuffers. + + /* + * It is important that we break on error, otherwise we might end + * up owning reference counts on numerous errorBuffers. */ + break; } } if (result != TCL_OK) { if (errfile == NULL) { - /* - * We try to accomodate poor error results from our - * Tcl_FS calls + /* + * We try to accomodate poor error results from our Tcl_FS calls. */ - Tcl_AppendResult(interp, "error deleting unknown file: ", - Tcl_PosixError(interp), (char *) NULL); + + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "error deleting unknown file: %s", + Tcl_PosixError(interp))); } else { - Tcl_AppendResult(interp, "error deleting \"", - Tcl_GetString(errfile), "\": ", - Tcl_PosixError(interp), (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "error deleting \"%s\": %s", + TclGetString(errfile), Tcl_PosixError(interp))); } - } - done: + } + + done: if (errorBuffer != NULL) { Tcl_DecrRefCount(errorBuffer); } @@ -449,37 +444,37 @@ TclFileDeleteCmd(interp, objc, objv) * * CopyRenameOneFile * - * Copies or renames specified source file or directory hierarchy - * to the specified target. + * Copies or renames specified source file or directory hierarchy to the + * specified target. * * Results: * A standard Tcl result. * * Side effects: - * Target is overwritten if the force flag is set. Attempting to - * copy/rename a file onto a directory or a directory onto a file - * will always result in an error. + * Target is overwritten if the force flag is set. Attempting to + * copy/rename a file onto a directory or a directory onto a file will + * always result in an error. * *---------------------------------------------------------------------- */ static int -CopyRenameOneFile(interp, source, target, copyFlag, force) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Obj *source; /* Pathname of file to copy. May need to - * be translated. */ - Tcl_Obj *target; /* Pathname of file to create/overwrite. - * May need to be translated. */ - int copyFlag; /* If non-zero, copy files. Otherwise, - * rename them. */ - int force; /* If non-zero, overwrite target file if it - * exists. Otherwise, error if target already +CopyRenameOneFile( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Obj *source, /* Pathname of file to copy. May need to be + * translated. */ + Tcl_Obj *target, /* Pathname of file to create/overwrite. May + * need to be translated. */ + int copyFlag, /* If non-zero, copy files. Otherwise, rename + * them. */ + int force) /* If non-zero, overwrite target file if it + * exists. Otherwise, error if target already * exists. */ { int result; Tcl_Obj *errfile, *errorBuffer; - /* If source is a link, then this is the real file/directory */ - Tcl_Obj *actualSource = NULL; + Tcl_Obj *actualSource=NULL; /* If source is a link, then this is the real + * file/directory. */ Tcl_StatBuf sourceStatBuf, targetStatBuf; if (Tcl_FSConvertToPathType(interp, source) != TCL_OK) { @@ -488,16 +483,15 @@ CopyRenameOneFile(interp, source, target, copyFlag, force) if (Tcl_FSConvertToPathType(interp, target) != TCL_OK) { return TCL_ERROR; } - + errfile = NULL; errorBuffer = NULL; result = TCL_ERROR; - + /* - * We want to copy/rename links and not the files they point to, so we - * use lstat(). If target is a link, we also want to replace the - * link and not the file it points to, so we also use lstat() on the - * target. + * We want to copy/rename links and not the files they point to, so we use + * lstat(). If target is a link, we also want to replace the link and not + * the file it points to, so we also use lstat() on the target. */ if (Tcl_FSLstat(source, &sourceStatBuf) != 0) { @@ -523,7 +517,7 @@ CopyRenameOneFile(interp, source, target, copyFlag, force) * 16 bits and we get collisions. See bug #2015723. */ -#if !defined(WIN32) && !defined(__CYGWIN__) +#if !defined(_WIN32) && !defined(__CYGWIN__) if ((sourceStatBuf.st_ino != 0) && (targetStatBuf.st_ino != 0)) { if ((sourceStatBuf.st_ino == targetStatBuf.st_ino) && (sourceStatBuf.st_dev == targetStatBuf.st_dev)) { @@ -533,30 +527,48 @@ CopyRenameOneFile(interp, source, target, copyFlag, force) } #endif - /* - * Prevent copying/renaming a file onto a directory and - * vice-versa. This is a policy decision based on the fact that - * existing implementations of copy and rename on all platforms - * also prevent this. + * Prevent copying/renaming a file onto a directory and vice-versa. + * This is a policy decision based on the fact that existing + * implementations of copy and rename on all platforms also prevent + * this. */ if (S_ISDIR(sourceStatBuf.st_mode) - && !S_ISDIR(targetStatBuf.st_mode)) { + && !S_ISDIR(targetStatBuf.st_mode)) { errno = EISDIR; - Tcl_AppendResult(interp, "can't overwrite file \"", - Tcl_GetString(target), "\" with directory \"", - Tcl_GetString(source), "\"", (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't overwrite file \"%s\" with directory \"%s\"", + TclGetString(target), TclGetString(source))); goto done; } if (!S_ISDIR(sourceStatBuf.st_mode) - && S_ISDIR(targetStatBuf.st_mode)) { + && S_ISDIR(targetStatBuf.st_mode)) { errno = EISDIR; - Tcl_AppendResult(interp, "can't overwrite directory \"", - Tcl_GetString(target), "\" with file \"", - Tcl_GetString(source), "\"", (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't overwrite directory \"%s\" with file \"%s\"", + TclGetString(target), TclGetString(source))); goto done; } + + /* + * The destination exists, but appears to be ok to over-write, and + * -force is given. We now try to adjust permissions to ensure the + * operation succeeds. If we can't adjust permissions, we'll let the + * actual copy/rename return an error later. + */ + + { + Tcl_Obj *perm; + int index; + + TclNewLiteralStringObj(perm, "u+w"); + Tcl_IncrRefCount(perm); + if (TclFSFileAttrIndex(target, "-permissions", &index) == TCL_OK) { + Tcl_FSFileAttrsSet(NULL, index, target, perm); + } + Tcl_DecrRefCount(perm); + } } if (copyFlag == 0) { @@ -564,62 +576,96 @@ CopyRenameOneFile(interp, source, target, copyFlag, force) if (result == TCL_OK) { goto done; } - + if (errno == EINVAL) { - Tcl_AppendResult(interp, "error renaming \"", - Tcl_GetString(source), "\" to \"", - Tcl_GetString(target), "\": trying to rename a volume or ", - "move a directory into itself", (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "error renaming \"%s\" to \"%s\": trying to rename a" + " volume or move a directory into itself", + TclGetString(source), TclGetString(target))); goto done; } else if (errno != EXDEV) { errfile = target; goto done; } - + /* - * The rename failed because the move was across file systems. - * Fall through to copy file and then remove original. Note that - * the low-level Tcl_FSRenameFileProc in the filesystem is allowed - * to implement cross-filesystem moves itself, if it desires. + * The rename failed because the move was across file systems. Fall + * through to copy file and then remove original. Note that the + * low-level Tcl_FSRenameFileProc in the filesystem is allowed to + * implement cross-filesystem moves itself, if it desires. */ } actualSource = source; Tcl_IncrRefCount(actualSource); + + /* + * Activate the following block to copy files instead of links. However + * Tcl's semantics currently say we should copy links, so any such change + * should be the subject of careful study on the consequences. + * + * Perhaps there could be an optional flag to 'file copy' to dictate which + * approach to use, with the default being _not_ to have this block + * active. + */ + #if 0 #ifdef S_ISLNK - /* - * To add a flag to make 'copy' copy links instead of files, we could - * add a condition to ignore this 'if' here. - */ if (copyFlag && S_ISLNK(sourceStatBuf.st_mode)) { - /* - * We want to copy files not links. Therefore we must follow the - * link. There are two purposes to this 'stat' call here. First - * we want to know if the linked-file/dir actually exists, and - * second, in the block of code which follows, some 20 lines - * down, we want to check if the thing is a file or directory. + /* + * We want to copy files not links. Therefore we must follow the link. + * There are two purposes to this 'stat' call here. First we want to + * know if the linked-file/dir actually exists, and second, in the + * block of code which follows, some 20 lines down, we want to check + * if the thing is a file or directory. */ + if (Tcl_FSStat(source, &sourceStatBuf) != 0) { - /* Actual file doesn't exist */ - Tcl_AppendResult(interp, - "error copying \"", Tcl_GetString(source), - "\": the target of this link doesn't exist", - (char *) NULL); + /* + * Actual file doesn't exist. + */ + + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "error copying \"%s\": the target of this link doesn't" + " exist", TclGetString(source))); goto done; } else { int counter = 0; + while (1) { Tcl_Obj *path = Tcl_FSLink(actualSource, NULL, 0); if (path == NULL) { break; } + + /* + * Now we want to check if this is a relative path, and if so, + * to make it absolute. + */ + + if (Tcl_FSGetPathType(path) == TCL_PATH_RELATIVE) { + Tcl_Obj *abs = Tcl_FSJoinToPath(actualSource, 1, &path); + + if (abs == NULL) { + break; + } + Tcl_IncrRefCount(abs); + Tcl_DecrRefCount(path); + path = abs; + } Tcl_DecrRefCount(actualSource); actualSource = path; counter++; - /* Arbitrary limit of 20 links to follow */ + + /* + * Arbitrary limit of 20 links to follow. + */ + if (counter > 20) { - /* Too many links */ + /* + * Too many links. + */ + Tcl_SetErrno(EMLINK); errfile = source; goto done; @@ -628,47 +674,43 @@ CopyRenameOneFile(interp, source, target, copyFlag, force) /* Now 'actualSource' is the correct file */ } } -#endif +#endif /* S_ISLNK */ #endif if (S_ISDIR(sourceStatBuf.st_mode)) { result = Tcl_FSCopyDirectory(actualSource, target, &errorBuffer); if (result != TCL_OK) { if (errno == EXDEV) { - /* + /* * The copy failed because we're trying to do a - * cross-filesystem copy. We do this through our Tcl - * library. + * cross-filesystem copy. We do this through our Tcl library. */ - Tcl_SavedResult savedResult; - Tcl_Obj *copyCommand = Tcl_NewListObj(0,NULL); - Tcl_IncrRefCount(copyCommand); - Tcl_ListObjAppendElement(interp, copyCommand, - Tcl_NewStringObj("::tcl::CopyDirectory",-1)); + + Tcl_Obj *copyCommand, *cmdObj, *opObj; + + TclNewObj(copyCommand); + TclNewLiteralStringObj(cmdObj, "::tcl::CopyDirectory"); + Tcl_ListObjAppendElement(interp, copyCommand, cmdObj); if (copyFlag) { - Tcl_ListObjAppendElement(interp, copyCommand, - Tcl_NewStringObj("copying",-1)); + TclNewLiteralStringObj(opObj, "copying"); } else { - Tcl_ListObjAppendElement(interp, copyCommand, - Tcl_NewStringObj("renaming",-1)); + TclNewLiteralStringObj(opObj, "renaming"); } + Tcl_ListObjAppendElement(interp, copyCommand, opObj); Tcl_ListObjAppendElement(interp, copyCommand, source); Tcl_ListObjAppendElement(interp, copyCommand, target); - Tcl_SaveResult(interp, &savedResult); - result = Tcl_EvalObjEx(interp, copyCommand, - TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT); + Tcl_IncrRefCount(copyCommand); + result = Tcl_EvalObjEx(interp, copyCommand, + TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT); Tcl_DecrRefCount(copyCommand); if (result != TCL_OK) { - /* - * There was an error in the Tcl-level copy. - * We will pass on the Tcl error message and - * can ensure this by setting errfile to NULL + /* + * There was an error in the Tcl-level copy. We will pass + * on the Tcl error message and can ensure this by setting + * errfile to NULL */ - Tcl_DiscardResult(&savedResult); + errfile = NULL; - } else { - /* The copy was successful */ - Tcl_RestoreResult(interp, &savedResult); } } else { errfile = errorBuffer; @@ -685,26 +727,27 @@ CopyRenameOneFile(interp, source, target, copyFlag, force) result = TclCrossFilesystemCopy(interp, source, target); } if (result != TCL_OK) { - /* - * We could examine 'errno' to double-check if the problem - * was with the target, but we checked the source above, - * so it should be quite clear + /* + * We could examine 'errno' to double-check if the problem was + * with the target, but we checked the source above, so it should + * be quite clear */ + errfile = target; - /* - * We now need to reset the result, because the above call, - * if it failed, may have put an error message in place. - * (Ideally we would prefer not to pass an interpreter in - * above, but the channel IO code used by - * TclCrossFilesystemCopy currently requires one) - */ - Tcl_ResetResult(interp); } + /* + * We now need to reset the result, because the above call, + * may have left set it. (Ideally we would prefer not to pass + * an interpreter in above, but the channel IO code used by + * TclCrossFilesystemCopy currently requires one) + */ + Tcl_ResetResult(interp); } if ((copyFlag == 0) && (result == TCL_OK)) { if (S_ISDIR(sourceStatBuf.st_mode)) { result = Tcl_FSRemoveDirectory(source, 1, &errorBuffer); if (result != TCL_OK) { + errfile = errorBuffer; if (Tcl_FSEqualPaths(errfile, source) == 0) { errfile = source; } @@ -716,31 +759,30 @@ CopyRenameOneFile(interp, source, target, copyFlag, force) } } if (result != TCL_OK) { - Tcl_AppendResult(interp, "can't unlink \"", - Tcl_GetString(errfile), "\": ", - Tcl_PosixError(interp), (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("can't unlink \"%s\": %s", + TclGetString(errfile), Tcl_PosixError(interp))); errfile = NULL; } } - - done: + + done: if (errfile != NULL) { - Tcl_AppendResult(interp, - ((copyFlag) ? "error copying \"" : "error renaming \""), - Tcl_GetString(source), (char *) NULL); + Tcl_Obj *errorMsg = Tcl_ObjPrintf("error %s \"%s\"", + (copyFlag ? "copying" : "renaming"), TclGetString(source)); + if (errfile != source) { - Tcl_AppendResult(interp, "\" to \"", Tcl_GetString(target), - (char *) NULL); + Tcl_AppendPrintfToObj(errorMsg, " to \"%s\"", + TclGetString(target)); if (errfile != target) { - Tcl_AppendResult(interp, "\": \"", Tcl_GetString(errfile), - (char *) NULL); + Tcl_AppendPrintfToObj(errorMsg, ": \"%s\"", + TclGetString(errfile)); } } - Tcl_AppendResult(interp, "\": ", Tcl_PosixError(interp), - (char *) NULL); + Tcl_AppendPrintfToObj(errorMsg, ": %s", Tcl_PosixError(interp)); + Tcl_SetObjResult(interp, errorMsg); } if (errorBuffer != NULL) { - Tcl_DecrRefCount(errorBuffer); + Tcl_DecrRefCount(errorBuffer); } if (actualSource != NULL) { Tcl_DecrRefCount(actualSource); @@ -753,14 +795,13 @@ CopyRenameOneFile(interp, source, target, copyFlag, force) * * FileForceOption -- * - * Helps parse command line options for file commands that take - * the "-force" and "--" options. + * Helps parse command line options for file commands that take the + * "-force" and "--" options. * * Results: - * The return value is how many arguments from argv were consumed - * by this function, or -1 if there was an error parsing the - * options. If an error occurred, an error message is left in the - * interp's result. + * The return value is how many arguments from argv were consumed by this + * function, or -1 if there was an error parsing the options. If an error + * occurred, an error message is left in the interp's result. * * Side effects: * None. @@ -769,30 +810,33 @@ CopyRenameOneFile(interp, source, target, copyFlag, force) */ static int -FileForceOption(interp, objc, objv, forcePtr) - Tcl_Interp *interp; /* Interp, for error return. */ - int objc; /* Number of arguments. */ - Tcl_Obj *CONST objv[]; /* Argument strings. First command line +FileForceOption( + Tcl_Interp *interp, /* Interp, for error return. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[], /* Argument strings. First command line * option, if it exists, begins at 0. */ - int *forcePtr; /* If the "-force" was specified, *forcePtr - * is filled with 1, otherwise with 0. */ + int *forcePtr) /* If the "-force" was specified, *forcePtr is + * filled with 1, otherwise with 0. */ { - int force, i; - + int force, i, idx; + static const char *const options[] = { + "-force", "--", NULL + }; + force = 0; for (i = 0; i < objc; i++) { - if (Tcl_GetString(objv[i])[0] != '-') { + if (TclGetString(objv[i])[0] != '-') { break; } - if (strcmp(Tcl_GetString(objv[i]), "-force") == 0) { + if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", TCL_EXACT, + &idx) != TCL_OK) { + return -1; + } + if (idx == 0 /* -force */) { force = 1; - } else if (strcmp(Tcl_GetString(objv[i]), "--") == 0) { + } else { /* -- */ i++; break; - } else { - Tcl_AppendResult(interp, "bad option \"", Tcl_GetString(objv[i]), - "\": should be -force or --", (char *)NULL); - return -1; } } *forcePtr = force; @@ -805,13 +849,12 @@ FileForceOption(interp, objc, objv, forcePtr) * * Given a path in either tcl format (with / separators), or in the * platform-specific format for the current platform, return all the - * characters in the path after the last directory separator. But, - * if path is the root directory, returns no characters. + * characters in the path after the last directory separator. But, if + * path is the root directory, returns no characters. * * Results: - * Returns the string object that represents the basename. If there - * is an error, an error message is left in interp, and NULL is - * returned. + * Returns the string object that represents the basename. If there is an + * error, an error message is left in interp, and NULL is returned. * * Side effects: * None. @@ -820,23 +863,25 @@ FileForceOption(interp, objc, objv, forcePtr) */ static Tcl_Obj * -FileBasename(interp, pathPtr) - Tcl_Interp *interp; /* Interp, for error return. */ - Tcl_Obj *pathPtr; /* Path whose basename to extract. */ +FileBasename( + Tcl_Interp *interp, /* Interp, for error return. */ + Tcl_Obj *pathPtr) /* Path whose basename to extract. */ { int objc; Tcl_Obj *splitPtr; Tcl_Obj *resultPtr = NULL; - + splitPtr = Tcl_FSSplitPath(pathPtr, &objc); + Tcl_IncrRefCount(splitPtr); if (objc != 0) { - if ((objc == 1) && (*Tcl_GetString(pathPtr) == '~')) { + if ((objc == 1) && (*TclGetString(pathPtr) == '~')) { Tcl_DecrRefCount(splitPtr); if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) { return NULL; } splitPtr = Tcl_FSSplitPath(pathPtr, &objc); + Tcl_IncrRefCount(splitPtr); } /* @@ -847,7 +892,7 @@ FileBasename(interp, pathPtr) if (objc > 0) { Tcl_ListObjIndex(NULL, splitPtr, objc-1, &resultPtr); if ((objc == 1) && - (Tcl_FSGetPathType(resultPtr) != TCL_PATH_RELATIVE)) { + (Tcl_FSGetPathType(resultPtr) != TCL_PATH_RELATIVE)) { resultPtr = NULL; } } @@ -865,117 +910,158 @@ FileBasename(interp, pathPtr) * * TclFileAttrsCmd -- * - * Sets or gets the platform-specific attributes of a file. The - * objc-objv points to the file name with the rest of the command - * line following. This routine uses platform-specific tables of - * option strings and callbacks. The callback to get the - * attributes take three parameters: - * Tcl_Interp *interp; The interp to report errors with. - * Since this is an object-based API, - * the object form of the result should - * be used. - * CONST char *fileName; This is extracted using + * Sets or gets the platform-specific attributes of a file. The objc-objv + * points to the file name with the rest of the command line following. + * This routine uses platform-specific tables of option strings and + * callbacks. The callback to get the attributes take three parameters: + * Tcl_Interp *interp; The interp to report errors with. Since + * this is an object-based API, the object + * form of the result should be used. + * const char *fileName; This is extracted using * Tcl_TranslateFileName. - * TclObj **attrObjPtrPtr; A new object to hold the attribute - * is allocated and put here. + * TclObj **attrObjPtrPtr; A new object to hold the attribute is + * allocated and put here. * The first two parameters of the callback used to write out the * attributes are the same. The third parameter is: - * CONST *attrObjPtr; A pointer to the object that has - * the new attribute. - * They both return standard TCL errors; if the routine to get - * an attribute fails, no object is allocated and *attrObjPtrPtr - * is unchanged. + * const *attrObjPtr; A pointer to the object that has the new + * attribute. + * They both return standard TCL errors; if the routine to get an + * attribute fails, no object is allocated and *attrObjPtrPtr is + * unchanged. * * Results: - * Standard TCL error. + * Standard TCL error. * * Side effects: - * May set file attributes for the file name. - * + * May set file attributes for the file name. + * *---------------------------------------------------------------------- */ int -TclFileAttrsCmd(interp, objc, objv) - Tcl_Interp *interp; /* The interpreter for error reporting. */ - int objc; /* Number of command line arguments. */ - Tcl_Obj *CONST objv[]; /* The command line objects. */ +TclFileAttrsCmd( + ClientData clientData, /* Unused */ + Tcl_Interp *interp, /* The interpreter for error reporting. */ + int objc, /* Number of command line arguments. */ + Tcl_Obj *const objv[]) /* The command line objects. */ { int result; - CONST char ** attributeStrings; - Tcl_Obj* objStrings = NULL; + const char *const *attributeStrings; + const char **attributeStringsAllocated = NULL; + Tcl_Obj *objStrings = NULL; int numObjStrings = -1; Tcl_Obj *filePtr; - - if (objc < 3) { - Tcl_WrongNumArgs(interp, 2, objv, - "name ?option? ?value? ?option value ...?"); + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "name ?-option value ...?"); return TCL_ERROR; } - filePtr = objv[2]; + filePtr = objv[1]; if (Tcl_FSConvertToPathType(interp, filePtr) != TCL_OK) { - return TCL_ERROR; + return TCL_ERROR; } - - objc -= 3; - objv += 3; + + objc -= 2; + objv += 2; result = TCL_ERROR; Tcl_SetErrno(0); + + /* + * Get the set of attribute names from the filesystem. + */ + attributeStrings = Tcl_FSFileAttrStrings(filePtr, &objStrings); if (attributeStrings == NULL) { int index; Tcl_Obj *objPtr; + if (objStrings == NULL) { if (Tcl_GetErrno() != 0) { - /* - * There was an error, probably that the filePtr is - * not accepted by any filesystem + /* + * There was an error, probably that the filePtr is not + * accepted by any filesystem */ - Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), - "could not read \"", Tcl_GetString(filePtr), - "\": ", Tcl_PosixError(interp), - (char *) NULL); - return TCL_ERROR; + + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "could not read \"%s\": %s", + TclGetString(filePtr), Tcl_PosixError(interp))); } - goto end; + return TCL_ERROR; } - /* We own the object now */ + + /* + * We own the object now. + */ + Tcl_IncrRefCount(objStrings); - /* Use objStrings as a list object */ + + /* + * Use objStrings as a list object. + */ + if (Tcl_ListObjLength(interp, objStrings, &numObjStrings) != TCL_OK) { goto end; } - attributeStrings = (CONST char **) - ckalloc ((1+numObjStrings) * sizeof(char*)); + attributeStringsAllocated = (const char **) + TclStackAlloc(interp, (1+numObjStrings) * sizeof(char *)); for (index = 0; index < numObjStrings; index++) { Tcl_ListObjIndex(interp, objStrings, index, &objPtr); - attributeStrings[index] = Tcl_GetString(objPtr); + attributeStringsAllocated[index] = TclGetString(objPtr); } - attributeStrings[index] = NULL; + attributeStringsAllocated[index] = NULL; + attributeStrings = attributeStringsAllocated; + } else if (objStrings != NULL) { + Tcl_Panic("must not update objPtrRef's variable and return non-NULL"); } + + /* + * Process the attributes to produce a list of all of them, the value of a + * particular attribute, or to set one or more attributes (depending on + * the number of arguments). + */ + if (objc == 0) { /* * Get all attributes. */ - int index; + int index, res = TCL_OK, nbAtts = 0; Tcl_Obj *listPtr; - + listPtr = Tcl_NewListObj(0, NULL); for (index = 0; attributeStrings[index] != NULL; index++) { - Tcl_Obj *objPtr = Tcl_NewStringObj(attributeStrings[index], -1); - Tcl_ListObjAppendElement(interp, listPtr, objPtr); - /* We now forget about objPtr, it is in the list */ - objPtr = NULL; - if (Tcl_FSFileAttrsGet(interp, index, filePtr, - &objPtr) != TCL_OK) { - Tcl_DecrRefCount(listPtr); - goto end; + Tcl_Obj *objPtrAttr; + + if (res != TCL_OK) { + /* + * Clear the error from the last iteration. + */ + + Tcl_ResetResult(interp); } - Tcl_ListObjAppendElement(interp, listPtr, objPtr); + + res = Tcl_FSFileAttrsGet(interp, index, filePtr, &objPtrAttr); + if (res == TCL_OK) { + Tcl_Obj *objPtr = + Tcl_NewStringObj(attributeStrings[index], -1); + + Tcl_ListObjAppendElement(interp, listPtr, objPtr); + Tcl_ListObjAppendElement(interp, listPtr, objPtrAttr); + nbAtts++; + } + } + + if (index > 0 && nbAtts == 0) { + /* + * Error: no valid attributes found. + */ + + Tcl_DecrRefCount(listPtr); + goto end; } - Tcl_SetObjResult(interp, listPtr); + + Tcl_SetObjResult(interp, listPtr); } else if (objc == 1) { /* * Get one attribute. @@ -985,9 +1071,10 @@ TclFileAttrsCmd(interp, objc, objv) Tcl_Obj *objPtr = NULL; if (numObjStrings == 0) { - Tcl_AppendResult(interp, "bad option \"", - Tcl_GetString(objv[0]), "\", there are no file attributes" - " in this filesystem.", (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad option \"%s\", there are no file attributes in this" + " filesystem", TclGetString(objv[0]))); + Tcl_SetErrorCode(interp, "TCL","OPERATION","FATTR","NONE", NULL); goto end; } @@ -995,10 +1082,8 @@ TclFileAttrsCmd(interp, objc, objv) "option", 0, &index) != TCL_OK) { goto end; } - if (numObjStrings != -1 && objv[0]->typePtr != NULL - && objv[0]->typePtr->freeIntRepProc != NULL) { - objv[0]->typePtr->freeIntRepProc(objv[0]); - objv[0]->typePtr = NULL; + if (attributeStringsAllocated != NULL) { + TclFreeIntRep(objv[0]); } if (Tcl_FSFileAttrsGet(interp, index, filePtr, &objPtr) != TCL_OK) { @@ -1011,49 +1096,418 @@ TclFileAttrsCmd(interp, objc, objv) */ int i, index; - + if (numObjStrings == 0) { - Tcl_AppendResult(interp, "bad option \"", - Tcl_GetString(objv[0]), "\", there are no file attributes" - " in this filesystem.", (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad option \"%s\", there are no file attributes in this" + " filesystem", TclGetString(objv[0]))); + Tcl_SetErrorCode(interp, "TCL","OPERATION","FATTR","NONE", NULL); goto end; } - for (i = 0; i < objc ; i += 2) { - if (Tcl_GetIndexFromObj(interp, objv[i], attributeStrings, + for (i = 0; i < objc ; i += 2) { + if (Tcl_GetIndexFromObj(interp, objv[i], attributeStrings, "option", 0, &index) != TCL_OK) { goto end; - } - if (numObjStrings != -1 && objv[i]->typePtr != NULL - && objv[i]->typePtr->freeIntRepProc != NULL) { - objv[i]->typePtr->freeIntRepProc(objv[i]); - objv[i]->typePtr = NULL; + } + if (attributeStringsAllocated != NULL) { + TclFreeIntRep(objv[i]); } if (i + 1 == objc) { - Tcl_AppendResult(interp, "value for \"", - Tcl_GetString(objv[i]), "\" missing", - (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "value for \"%s\" missing", TclGetString(objv[i]))); + Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FATTR", + "NOVALUE", NULL); goto end; } - if (Tcl_FSFileAttrsSet(interp, index, filePtr, - objv[i + 1]) != TCL_OK) { + if (Tcl_FSFileAttrsSet(interp, index, filePtr, + objv[i + 1]) != TCL_OK) { goto end; - } - } + } + } } result = TCL_OK; - end: - if (numObjStrings != -1) { - /* Free up the array we allocated */ - ckfree((char*)attributeStrings); - /* - * We don't need this object that was passed to us - * any more. + /* + * Free up the array we allocated and drop our reference to any list of + * attribute names issued by the filesystem. + */ + + end: + if (attributeStringsAllocated != NULL) { + TclStackFree(interp, (void *) attributeStringsAllocated); + } + if (objStrings != NULL) { + Tcl_DecrRefCount(objStrings); + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclFileLinkCmd -- + * + * This function is invoked to process the "file link" Tcl command. See + * the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * May create a new link. + * + *---------------------------------------------------------------------- + */ + +int +TclFileLinkCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_Obj *contents; + int index; + + if (objc < 2 || objc > 4) { + Tcl_WrongNumArgs(interp, 1, objv, "?-linktype? linkname ?target?"); + return TCL_ERROR; + } + + /* + * Index of the 'source' argument. + */ + + if (objc == 4) { + index = 2; + } else { + index = 1; + } + + if (objc > 2) { + int linkAction; + + if (objc == 4) { + /* + * We have a '-linktype' argument. + */ + + static const char *const linkTypes[] = { + "-symbolic", "-hard", NULL + }; + if (Tcl_GetIndexFromObj(interp, objv[1], linkTypes, "switch", 0, + &linkAction) != TCL_OK) { + return TCL_ERROR; + } + if (linkAction == 0) { + linkAction = TCL_CREATE_SYMBOLIC_LINK; + } else { + linkAction = TCL_CREATE_HARD_LINK; + } + } else { + linkAction = TCL_CREATE_SYMBOLIC_LINK | TCL_CREATE_HARD_LINK; + } + if (Tcl_FSConvertToPathType(interp, objv[index]) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Create link from source to target. */ - if (objStrings != NULL) { - Tcl_DecrRefCount(objStrings); + + contents = Tcl_FSLink(objv[index], objv[index+1], linkAction); + if (contents == NULL) { + /* + * We handle three common error cases specially, and for all other + * errors, we use the standard posix error message. + */ + + if (errno == EEXIST) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "could not create new link \"%s\": that path already" + " exists", TclGetString(objv[index]))); + Tcl_PosixError(interp); + } else if (errno == ENOENT) { + /* + * There are two cases here: either the target doesn't exist, + * or the directory of the src doesn't exist. + */ + + int access; + Tcl_Obj *dirPtr = TclPathPart(interp, objv[index], + TCL_PATH_DIRNAME); + + if (dirPtr == NULL) { + return TCL_ERROR; + } + access = Tcl_FSAccess(dirPtr, F_OK); + Tcl_DecrRefCount(dirPtr); + if (access != 0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "could not create new link \"%s\": no such file" + " or directory", TclGetString(objv[index]))); + Tcl_PosixError(interp); + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "could not create new link \"%s\": target \"%s\" " + "doesn't exist", TclGetString(objv[index]), + TclGetString(objv[index+1]))); + errno = ENOENT; + Tcl_PosixError(interp); + } + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "could not create new link \"%s\" pointing to \"%s\": %s", + TclGetString(objv[index]), + TclGetString(objv[index+1]), Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + } else { + if (Tcl_FSConvertToPathType(interp, objv[index]) != TCL_OK) { + return TCL_ERROR; + } + + /* + * Read link + */ + + contents = Tcl_FSLink(objv[index], NULL, 0); + if (contents == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "could not read link \"%s\": %s", + TclGetString(objv[index]), Tcl_PosixError(interp))); + return TCL_ERROR; } } - return result; + Tcl_SetObjResult(interp, contents); + if (objc == 2) { + /* + * If we are reading a link, we need to free this result refCount. If + * we are creating a link, this will just be objv[index+1], and so we + * don't own it. + */ + + Tcl_DecrRefCount(contents); + } + return TCL_OK; } + +/* + *---------------------------------------------------------------------- + * + * TclFileReadLinkCmd -- + * + * This function is invoked to process the "file readlink" Tcl command. + * See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclFileReadLinkCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_Obj *contents; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 1, objv, "name"); + return TCL_ERROR; + } + + if (Tcl_FSConvertToPathType(interp, objv[1]) != TCL_OK) { + return TCL_ERROR; + } + + contents = Tcl_FSLink(objv[1], NULL, 0); + + if (contents == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "could not read link \"%s\": %s", + TclGetString(objv[1]), Tcl_PosixError(interp))); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, contents); + Tcl_DecrRefCount(contents); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TclFileTemporaryCmd + * + * This function implements the "tempfile" subcommand of the "file" + * command. + * + * Results: + * Returns a standard Tcl result. + * + * Side effects: + * Creates a temporary file. Opens a channel to that file and puts the + * name of that channel in the result. *Might* register suitable exit + * handlers to ensure that the temporary file gets deleted. Might write + * to a variable, so reentrancy is a potential issue. + * + *--------------------------------------------------------------------------- + */ + +int +TclFileTemporaryCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_Obj *nameVarObj = NULL; /* Variable to store the name of the temporary + * file in. */ + Tcl_Obj *nameObj = NULL; /* Object that will contain the filename. */ + Tcl_Channel chan; /* The channel opened (RDWR) on the temporary + * file, or NULL if there's an error. */ + Tcl_Obj *tempDirObj = NULL, *tempBaseObj = NULL, *tempExtObj = NULL; + /* Pieces of template. Each piece is NULL if + * it is omitted. The platform temporary file + * engine might ignore some pieces. */ + + if (objc < 1 || objc > 3) { + Tcl_WrongNumArgs(interp, 1, objv, "?nameVar? ?template?"); + return TCL_ERROR; + } + + if (objc > 1) { + nameVarObj = objv[1]; + TclNewObj(nameObj); + } + if (objc > 2) { + int length; + Tcl_Obj *templateObj = objv[2]; + const char *string = TclGetStringFromObj(templateObj, &length); + + /* + * Treat an empty string as if it wasn't there. + */ + + if (length == 0) { + goto makeTemporary; + } + + /* + * The template only gives a directory if there is a directory + * separator in it. + */ + + if (strchr(string, '/') != NULL + || (tclPlatform == TCL_PLATFORM_WINDOWS + && strchr(string, '\\') != NULL)) { + tempDirObj = TclPathPart(interp, templateObj, TCL_PATH_DIRNAME); + + /* + * Only allow creation of temporary files in the native filesystem + * since they are frequently used for integration with external + * tools or system libraries. [Bug 2388866] + */ + + if (tempDirObj != NULL && Tcl_FSGetFileSystemForPath(tempDirObj) + != &tclNativeFilesystem) { + TclDecrRefCount(tempDirObj); + tempDirObj = NULL; + } + } + + /* + * The template only gives the filename if the last character isn't a + * directory separator. + */ + + if (string[length-1] != '/' && (tclPlatform != TCL_PLATFORM_WINDOWS + || string[length-1] != '\\')) { + Tcl_Obj *tailObj = TclPathPart(interp, templateObj, + TCL_PATH_TAIL); + + if (tailObj != NULL) { + tempBaseObj = TclPathPart(interp, tailObj, TCL_PATH_ROOT); + tempExtObj = TclPathPart(interp, tailObj, TCL_PATH_EXTENSION); + TclDecrRefCount(tailObj); + } + } + } + + /* + * Convert empty parts of the template into unspecified parts. + */ + + if (tempDirObj && !TclGetString(tempDirObj)[0]) { + TclDecrRefCount(tempDirObj); + tempDirObj = NULL; + } + if (tempBaseObj && !TclGetString(tempBaseObj)[0]) { + TclDecrRefCount(tempBaseObj); + tempBaseObj = NULL; + } + if (tempExtObj && !TclGetString(tempExtObj)[0]) { + TclDecrRefCount(tempExtObj); + tempExtObj = NULL; + } + + /* + * Create and open the temporary file. + */ + + makeTemporary: + chan = TclpOpenTemporaryFile(tempDirObj,tempBaseObj,tempExtObj, nameObj); + + /* + * If we created pieces of template, get rid of them now. + */ + + if (tempDirObj) { + TclDecrRefCount(tempDirObj); + } + if (tempBaseObj) { + TclDecrRefCount(tempBaseObj); + } + if (tempExtObj) { + TclDecrRefCount(tempExtObj); + } + + /* + * Deal with results. + */ + + if (chan == NULL) { + if (nameVarObj) { + TclDecrRefCount(nameObj); + } + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't create temporary file: %s", Tcl_PosixError(interp))); + return TCL_ERROR; + } + Tcl_RegisterChannel(interp, chan); + if (nameVarObj != NULL) { + if (Tcl_ObjSetVar2(interp, nameVarObj, NULL, nameObj, + TCL_LEAVE_ERR_MSG) == NULL) { + Tcl_UnregisterChannel(interp, chan); + return TCL_ERROR; + } + } + Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_GetChannelName(chan), -1)); + return TCL_OK; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |