From fd0444bf94a1f315cb724abf0374890fb9b7e310 Mon Sep 17 00:00:00 2001 From: dkf Date: Tue, 30 Apr 2019 18:31:47 +0000 Subject: Implementation of [file tempdir]; hand-tested on OSX... --- generic/tclCmdAH.c | 1 + generic/tclFCmd.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++- generic/tclInt.h | 1 + unix/tclUnixFCmd.c | 4 +- win/tclWinFCmd.c | 27 +++++++--- 5 files changed, 172 insertions(+), 8 deletions(-) diff --git a/generic/tclCmdAH.c b/generic/tclCmdAH.c index 1811c5c..dd98ef3 100644 --- a/generic/tclCmdAH.c +++ b/generic/tclCmdAH.c @@ -1093,6 +1093,7 @@ TclInitFileCmd( {"stat", FileAttrStatCmd, TclCompileBasic2ArgCmd, NULL, NULL, 1}, {"system", PathFilesystemCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 0}, {"tail", PathTailCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, + {"tempdir", TclFileTempDirCmd, TclCompileBasic0Or1ArgCmd, NULL, NULL, 1}, {"tempfile", TclFileTemporaryCmd, TclCompileBasic0To2ArgCmd, NULL, NULL, 1}, {"type", FileAttrTypeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"volumes", FilesystemVolumesCmd, TclCompileBasic0ArgCmd, NULL, NULL, 1}, diff --git a/generic/tclFCmd.c b/generic/tclFCmd.c index a4dded2..8ef0456 100644 --- a/generic/tclFCmd.c +++ b/generic/tclFCmd.c @@ -1345,7 +1345,7 @@ TclFileReadLinkCmd( /* *--------------------------------------------------------------------------- * - * TclFileTemporaryCmd + * TclFileTemporaryCmd -- * * This function implements the "tempfile" subcommand of the "file" * command. @@ -1505,6 +1505,151 @@ TclFileTemporaryCmd( } /* + *--------------------------------------------------------------------------- + * + * TclFileTempDirCmd -- + * + * This function implements the "tempdir" subcommand of the "file" + * command. + * + * Results: + * Returns a standard Tcl result. + * + * Side effects: + * Creates a temporary directory. + * + *--------------------------------------------------------------------------- + */ + +int +TclFileTempDirCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_Obj *dirNameObj; /* Object that will contain the directory + * name. */ + Tcl_Obj *baseDirObj = NULL, *nameBaseObj = 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 > 2) { + Tcl_WrongNumArgs(interp, 1, objv, "?template?"); + return TCL_ERROR; + } + + if (objc > 1) { + int length; + Tcl_Obj *templateObj = objv[1]; + const char *string = TclGetStringFromObj(templateObj, &length); + const int onWindows = (tclPlatform == TCL_PLATFORM_WINDOWS); + + /* + * 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, and only gives a base name if there's at least one + * character after the last directory separator. + */ + + if (strchr(string, '/') == NULL + && (!onWindows || strchr(string, '\\') == NULL)) { + /* + * No directory separator, so just assume we have a file name. + * This is a bit wrong on Windows where we could have problems + * with disk name prefixes... but those are much less common in + * naked form so we just pass through and let the OS figure it out + * instead. + */ + + nameBaseObj = templateObj; + Tcl_IncrRefCount(nameBaseObj); + } else if (string[length-1] != '/' + && (!onWindows || string[length-1] != '\\')) { + /* + * If the template has a non-terminal directory separator, split + * into dirname and tail. + */ + + baseDirObj = TclPathPart(interp, templateObj, TCL_PATH_DIRNAME); + nameBaseObj = TclPathPart(interp, templateObj, TCL_PATH_TAIL); + } else { + /* + * Otherwise, there must be a terminal directory separator, so + * just the directory is given. + */ + + baseDirObj = templateObj; + Tcl_IncrRefCount(baseDirObj); + } + + /* + * Only allow creation of temporary directories in the native + * filesystem since they are frequently used for integration with + * external tools or system libraries. + */ + + if (baseDirObj != NULL && Tcl_FSGetFileSystemForPath(baseDirObj) + != &tclNativeFilesystem) { + TclDecrRefCount(baseDirObj); + baseDirObj = NULL; + } + } + + /* + * Convert empty parts of the template into unspecified parts. + */ + + if (baseDirObj && !TclGetString(baseDirObj)[0]) { + TclDecrRefCount(baseDirObj); + baseDirObj = NULL; + } + if (nameBaseObj && !TclGetString(nameBaseObj)[0]) { + TclDecrRefCount(nameBaseObj); + nameBaseObj = NULL; + } + + /* + * Create and open the temporary file. + */ + + makeTemporary: + dirNameObj = TclpCreateTemporaryDirectory(baseDirObj, nameBaseObj); + + /* + * If we created pieces of template, get rid of them now. + */ + + if (baseDirObj) { + TclDecrRefCount(baseDirObj); + } + if (nameBaseObj) { + TclDecrRefCount(nameBaseObj); + } + + /* + * Deal with results. + */ + + if (dirNameObj == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't create temporary directory: %s", + Tcl_PosixError(interp))); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, dirNameObj); + return TCL_OK; +} + +/* * Local Variables: * mode: c * c-basic-offset: 4 diff --git a/generic/tclInt.h b/generic/tclInt.h index 3db1264..9a2ef85 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2967,6 +2967,7 @@ MODULE_SCOPE Tcl_ObjCmdProc TclFileLinkCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileMakeDirsCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileReadLinkCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileRenameCmd; +MODULE_SCOPE Tcl_ObjCmdProc TclFileTempDirCmd; MODULE_SCOPE Tcl_ObjCmdProc TclFileTemporaryCmd; MODULE_SCOPE void TclCreateLateExitHandler(Tcl_ExitProc *proc, ClientData clientData); diff --git a/unix/tclUnixFCmd.c b/unix/tclUnixFCmd.c index 861ff66..3e1a5c2 100644 --- a/unix/tclUnixFCmd.c +++ b/unix/tclUnixFCmd.c @@ -2314,7 +2314,9 @@ TclpCreateTemporaryDirectory( Tcl_DStringAppend(&template, DefaultTempDir(), -1); /* INTL: native */ } - TclDStringAppendLiteral(&template, "/"); + if (Tcl_DStringValue(&template)[Tcl_DStringLength(&template) - 1] != '/') { + TclDStringAppendLiteral(&template, "/"); + } if (basenameObj) { string = TclGetString(basenameObj); diff --git a/win/tclWinFCmd.c b/win/tclWinFCmd.c index c626ece..a08d54a 100644 --- a/win/tclWinFCmd.c +++ b/win/tclWinFCmd.c @@ -1981,6 +1981,7 @@ TclpCreateTemporaryDirectory( { Tcl_DString base, name; /* Contains WCHARs */ int baseLen; + DWORD error; /* * Build the path in writable memory from the user-supplied pieces and @@ -1993,8 +1994,8 @@ TclpCreateTemporaryDirectory( goto useSystemTemp; } Tcl_WinUtfToTChar(Tcl_GetString(dirObj), -1, &base); - if (dirObj->bytes[dirObj->length - 1] != '/') { - TclUtfToWCharDString("/", -1, &base); + if (dirObj->bytes[dirObj->length - 1] != '\\') { + TclUtfToWCharDString("\\", -1, &base); } } else { useSystemTemp: @@ -2022,9 +2023,10 @@ TclpCreateTemporaryDirectory( TclUtfToWCharDString("_", -1, &base); /* - * Now we keep on trying random suffixes until we get one that works. Note - * that SUFFIX_LENGTH is longer than on Unix because we expect to be not - * on a case-sensitive filesystem. + * Now we keep on trying random suffixes until we get one that works + * (i.e., that doesn't trigger the ERROR_ALREADY_EXISTS error). Note that + * SUFFIX_LENGTH is longer than on Unix because we expect to be not on a + * case-sensitive filesystem. */ baseLen = Tcl_DStringLength(&base); @@ -2039,13 +2041,26 @@ TclpCreateTemporaryDirectory( * Put a random suffix on the end. */ + error = ERROR_SUCCESS; tempbuf[SUFFIX_LENGTH] = '\0'; for (i = 0 ; i < SUFFIX_LENGTH; i++) { tempbuf[i] = randChars[(int) (rand() * numRandChars)]; } Tcl_DStringSetLength(&base, baseLen); TclUtfToWCharDString(tempbuf, -1, &base); - } while (!CreateDirectoryW((LPCWSTR) Tcl_DStringValue(&base), NULL)); + } while (!CreateDirectoryW((LPCWSTR) Tcl_DStringValue(&base), NULL) + && (error = GetLastError()) == ERROR_ALREADY_EXISTS); + + /* + * Check for other errors. The big ones are ERROR_PATH_NOT_FOUND and + * ERROR_ACCESS_DENIED. + */ + + if (error != ERROR_SUCCESS) { + TclWinConvertError(error); + Tcl_DStringFree(&base); + return NULL; + } /* * We actually made the directory, so we're done! Report what we made back -- cgit v0.12