Index: doc/file.n ================================================================== --- doc/file.n +++ doc/file.n @@ -430,10 +430,40 @@ Returns all of the characters in the last filesystem component of \fIname\fR. Any trailing directory separator in \fIname\fR is ignored. If \fIname\fR contains no separators then returns \fIname\fR. So, \fBfile tail a/b\fR, \fBfile tail a/b/\fR and \fBfile tail b\fR all return \fBb\fR. +.TP +\fBfile tempdir\fR ?\fItemplate\fR? +.VS "8.7, TIP 431" +Creates a temporary directory (guaranteed to be newly created and writable by +the current script) and returns its name. If \fItemplate\fR is given, it +specifies one of or both of the existing directory (on a filesystem controlled +by the operating system) to contain the temporary directory, and the base part +of the directory name; it is considered to have the location of the directory +if there is a directory separator in the name, and the base part is everything +after the last directory separator (if non-empty). The default containing +directory is determined by system-specific operations, and the default base +name prefix is +.QW \fBtcl\fR . +.RS +.PP +The following output is typical and illustrative; the actual output will vary +between platforms: +.PP +.CS +% \fBfile tempdir\fR +/var/tmp/tcl_u0kuy5 + % \fBfile tempdir\fR /tmp/myapp +/tmp/myapp_8o7r9L +% \fBfile tempdir\fR /tmp/ +/tmp/tcl_1mOJHD +% \fBfile tempdir\fR myapp +/var/tmp/myapp_0ihS0n +.CE +.RE +.VE "8.7, TIP 431" .TP \fBfile tempfile\fR ?\fInameVar\fR? ?\fItemplate\fR? '\" TIP #210 .VS 8.6 Creates a temporary file and returns a read-write channel opened on that file. Index: generic/tclBasic.c ================================================================== --- generic/tclBasic.c +++ generic/tclBasic.c @@ -364,10 +364,11 @@ {"file", "rename"}, {"file", "rootname"}, {"file", "size"}, {"file", "stat"}, {"file", "tail"}, + {"file", "tempdir"}, {"file", "tempfile"}, {"file", "type"}, {"file", "volumes"}, {"file", "writable"}, /* [info] has two unsafe commands */ Index: generic/tclCmdAH.c ================================================================== --- generic/tclCmdAH.c +++ generic/tclCmdAH.c @@ -1091,10 +1091,11 @@ {"size", FileAttrSizeCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {"split", PathSplitCmd, TclCompileBasic1ArgCmd, NULL, NULL, 0}, {"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}, {"writable", FileAttrIsWritableCmd, TclCompileBasic1ArgCmd, NULL, NULL, 1}, {NULL, NULL, NULL, NULL, NULL, 0} Index: generic/tclFCmd.c ================================================================== --- generic/tclFCmd.c +++ generic/tclFCmd.c @@ -1343,11 +1343,11 @@ } /* *--------------------------------------------------------------------------- * - * TclFileTemporaryCmd + * TclFileTemporaryCmd -- * * This function implements the "tempfile" subcommand of the "file" * command. * * Results: @@ -1501,13 +1501,158 @@ } } Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_GetChannelName(chan), -1)); return TCL_OK; } + +/* + *--------------------------------------------------------------------------- + * + * 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 * fill-column: 78 * End: */ Index: generic/tclInt.decls ================================================================== --- generic/tclInt.decls +++ generic/tclInt.decls @@ -1026,10 +1026,16 @@ } declare 257 { void TclStaticPackage(Tcl_Interp *interp, const char *pkgName, Tcl_PackageInitProc *initProc, Tcl_PackageInitProc *safeInitProc) } + +# TIP 431: temporary directory creation function +declare 258 { + Tcl_Obj *TclpCreateTemporaryDirectory(Tcl_Obj *dirObj, + Tcl_Obj *basenameObj) +} ############################################################################## # Define the platform specific internal Tcl interface. These functions are # only available on the designated platform. Index: generic/tclInt.h ================================================================== --- generic/tclInt.h +++ generic/tclInt.h @@ -2965,10 +2965,11 @@ MODULE_SCOPE Tcl_ObjCmdProc TclFileDeleteCmd; 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); MODULE_SCOPE void TclDeleteLateExitHandler(Tcl_ExitProc *proc, ClientData clientData); Index: generic/tclIntDecls.h ================================================================== --- generic/tclIntDecls.h +++ generic/tclIntDecls.h @@ -651,10 +651,13 @@ /* 257 */ EXTERN void TclStaticPackage(Tcl_Interp *interp, const char *pkgName, Tcl_PackageInitProc *initProc, Tcl_PackageInitProc *safeInitProc); +/* 258 */ +EXTERN Tcl_Obj * TclpCreateTemporaryDirectory(Tcl_Obj *dirObj, + Tcl_Obj *basenameObj); typedef struct TclIntStubs { int magic; void *hooks; @@ -914,10 +917,11 @@ Tcl_Obj * (*tclPtrSetVar) (Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, const int flags); /* 253 */ Tcl_Obj * (*tclPtrIncrObjVar) (Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *incrPtr, const int flags); /* 254 */ int (*tclPtrObjMakeUpvar) (Tcl_Interp *interp, Tcl_Var otherPtr, Tcl_Obj *myNamePtr, int myFlags); /* 255 */ int (*tclPtrUnsetVar) (Tcl_Interp *interp, Tcl_Var varPtr, Tcl_Var arrayPtr, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, const int flags); /* 256 */ void (*tclStaticPackage) (Tcl_Interp *interp, const char *pkgName, Tcl_PackageInitProc *initProc, Tcl_PackageInitProc *safeInitProc); /* 257 */ + Tcl_Obj * (*tclpCreateTemporaryDirectory) (Tcl_Obj *dirObj, Tcl_Obj *basenameObj); /* 258 */ } TclIntStubs; extern const TclIntStubs *tclIntStubsPtr; #ifdef __cplusplus @@ -1357,10 +1361,12 @@ (tclIntStubsPtr->tclPtrObjMakeUpvar) /* 255 */ #define TclPtrUnsetVar \ (tclIntStubsPtr->tclPtrUnsetVar) /* 256 */ #define TclStaticPackage \ (tclIntStubsPtr->tclStaticPackage) /* 257 */ +#define TclpCreateTemporaryDirectory \ + (tclIntStubsPtr->tclpCreateTemporaryDirectory) /* 258 */ #endif /* defined(USE_TCL_STUBS) */ /* !END!: Do not edit above this line. */ Index: generic/tclStubInit.c ================================================================== --- generic/tclStubInit.c +++ generic/tclStubInit.c @@ -749,10 +749,11 @@ TclPtrSetVar, /* 253 */ TclPtrIncrObjVar, /* 254 */ TclPtrObjMakeUpvar, /* 255 */ TclPtrUnsetVar, /* 256 */ TclStaticPackage, /* 257 */ + TclpCreateTemporaryDirectory, /* 258 */ }; static const TclIntPlatStubs tclIntPlatStubs = { TCL_STUB_MAGIC, 0, Index: tests/cmdAH.test ================================================================== --- tests/cmdAH.test +++ tests/cmdAH.test @@ -233,11 +233,11 @@ test cmdAH-5.1 {Tcl_FileObjCmd} -returnCodes error -body { file } -result {wrong # args: should be "file subcommand ?arg ...?"} test cmdAH-5.2 {Tcl_FileObjCmd} -returnCodes error -body { file x -} -result {unknown or ambiguous subcommand "x": must be atime, attributes, channels, copy, delete, dirname, executable, exists, extension, isdirectory, isfile, join, link, lstat, mkdir, mtime, nativename, normalize, owned, pathtype, readable, readlink, rename, rootname, separator, size, split, stat, system, tail, tempfile, type, volumes, or writable} +} -result {unknown or ambiguous subcommand "x": must be atime, attributes, channels, copy, delete, dirname, executable, exists, extension, isdirectory, isfile, join, link, lstat, mkdir, mtime, nativename, normalize, owned, pathtype, readable, readlink, rename, rootname, separator, size, split, stat, system, tail, tempdir, tempfile, type, volumes, or writable} test cmdAH-5.3 {Tcl_FileObjCmd} -returnCodes error -body { file exists } -result {wrong # args: should be "file exists name"} test cmdAH-5.4 {Tcl_FileObjCmd} { file exists "" @@ -1524,11 +1524,11 @@ } -result "characterSpecial" # Error conditions test cmdAH-30.1 {Tcl_FileObjCmd: error conditions} -returnCodes error -body { file gorp x -} -result {unknown or ambiguous subcommand "gorp": must be atime, attributes, channels, copy, delete, dirname, executable, exists, extension, isdirectory, isfile, join, link, lstat, mkdir, mtime, nativename, normalize, owned, pathtype, readable, readlink, rename, rootname, separator, size, split, stat, system, tail, tempfile, type, volumes, or writable} +} -result {unknown or ambiguous subcommand "gorp": must be atime, attributes, channels, copy, delete, dirname, executable, exists, extension, isdirectory, isfile, join, link, lstat, mkdir, mtime, nativename, normalize, owned, pathtype, readable, readlink, rename, rootname, separator, size, split, stat, system, tail, tempdir, tempfile, type, volumes, or writable} test cmdAH-30.2 {Tcl_FileObjCmd: error conditions} -returnCodes error -body { file ex x } -match glob -result {unknown or ambiguous subcommand "ex": must be *} test cmdAH-30.3 {Tcl_FileObjCmd: error conditions} -returnCodes error -body { file is x @@ -1656,10 +1656,66 @@ expr {[string match $template*.bar $name] ? "ok" : "$template.bar produced $name"} } -constraints {unix nonPortable} -cleanup { catch {file delete $name} } -result ok + +test cmdAH-33.1 {file tempdir} -body { + file tempdir a b +} -returnCodes error -result {wrong # args: should be "file tempdir ?template?"} +test cmdAH-33.2 {file tempdir} -body { + set d [file tempdir] + list [file tail $d] [file exists $d] [file type $d] \ + [glob -nocomplain -directory $d *] +} -match glob -result {tcl_* 1 directory {}} -cleanup { + catch {file delete $d} +} +test cmdAH-33.3 {file tempdir} -body { + set d [file tempdir gorp] + list [file tail $d] [file exists $d] [file type $d] \ + [glob -nocomplain -directory $d *] +} -match glob -result {gorp_* 1 directory {}} -cleanup { + catch {file delete $d} +} +test cmdAH-33.4 {file tempdir} -setup { + set base [file join [temporaryDirectory] gorp] + file mkdir $base +} -body { + set pre [glob -nocomplain -directory $base *] + set d [file normalize [file tempdir $base/]] + list [string map [list $base GORP:] $d] [file exists $d] [file type $d] \ + $pre [glob -nocomplain -directory $d *] +} -match glob -result {GORP:/tcl_* 1 directory {} {}} -cleanup { + catch {file delete -force $base} +} +test cmdAH-33.5 {file tempdir} -setup { + set base [file join [temporaryDirectory] gorp] + file mkdir $base +} -body { + set pre [glob -nocomplain -directory $base *] + set d [file normalize [file tempdir $base/gorp]] + list [string map [list $base GORP:] $d] [file exists $d] [file type $d] \ + $pre [glob -nocomplain -directory $d *] +} -match glob -result {GORP:/gorp_* 1 directory {} {}} -cleanup { + catch {file delete -force $base} +} +test cmdAH-33.6 {file tempdir: missing parent dir} -setup { + set base [file join [temporaryDirectory] gorp] + file mkdir $base +} -returnCodes error -body { + file tempdir $base/quux/ +} -cleanup { + catch {file delete -force $base} +} -result {can't create temporary directory: no such file or directory} +test cmdAH-33.7 {file tempdir: missing parent dir} -setup { + set base [file join [temporaryDirectory] gorp] + file mkdir $base +} -returnCodes error -body { + file tempdir $base/quux/foobar +} -cleanup { + catch {file delete -force $base} +} -result {can't create temporary directory: no such file or directory} # This shouldn't work, but just in case a test above failed... catch {close $newFileId} interp delete safeInterp Index: tests/interp.test ================================================================== --- tests/interp.test +++ tests/interp.test @@ -18,11 +18,11 @@ ::tcltest::loadTestedCommands catch [list package require -exact Tcltest [info patchlevel]] testConstraint testinterpdelete [llength [info commands testinterpdelete]] -set hidden_cmds {cd encoding exec exit fconfigure file glob load open pwd socket source tcl:encoding:dirs tcl:encoding:system tcl:file:atime tcl:file:attributes tcl:file:copy tcl:file:delete tcl:file:dirname tcl:file:executable tcl:file:exists tcl:file:extension tcl:file:isdirectory tcl:file:isfile tcl:file:link tcl:file:lstat tcl:file:mkdir tcl:file:mtime tcl:file:nativename tcl:file:normalize tcl:file:owned tcl:file:readable tcl:file:readlink tcl:file:rename tcl:file:rootname tcl:file:size tcl:file:stat tcl:file:tail tcl:file:tempfile tcl:file:type tcl:file:volumes tcl:file:writable tcl:info:cmdtype tcl:info:nameofexecutable tcl:process:autopurge tcl:process:list tcl:process:purge tcl:process:status tcl:zipfs:lmkimg tcl:zipfs:lmkzip tcl:zipfs:mkimg tcl:zipfs:mkkey tcl:zipfs:mkzip tcl:zipfs:mount tcl:zipfs:mount_data tcl:zipfs:unmount unload} +set hidden_cmds {cd encoding exec exit fconfigure file glob load open pwd socket source tcl:encoding:dirs tcl:encoding:system tcl:file:atime tcl:file:attributes tcl:file:copy tcl:file:delete tcl:file:dirname tcl:file:executable tcl:file:exists tcl:file:extension tcl:file:isdirectory tcl:file:isfile tcl:file:link tcl:file:lstat tcl:file:mkdir tcl:file:mtime tcl:file:nativename tcl:file:normalize tcl:file:owned tcl:file:readable tcl:file:readlink tcl:file:rename tcl:file:rootname tcl:file:size tcl:file:stat tcl:file:tail tcl:file:tempdir tcl:file:tempfile tcl:file:type tcl:file:volumes tcl:file:writable tcl:info:cmdtype tcl:info:nameofexecutable tcl:process:autopurge tcl:process:list tcl:process:purge tcl:process:status tcl:zipfs:lmkimg tcl:zipfs:lmkzip tcl:zipfs:mkimg tcl:zipfs:mkkey tcl:zipfs:mkzip tcl:zipfs:mount tcl:zipfs:mount_data tcl:zipfs:unmount unload} foreach i [interp slaves] { interp delete $i } Index: unix/tclUnixFCmd.c ================================================================== --- unix/tclUnixFCmd.c +++ unix/tclUnixFCmd.c @@ -2270,10 +2270,89 @@ * isn't. */ return TCL_TEMPORARY_FILE_DIRECTORY; } + +/* + *---------------------------------------------------------------------- + * + * TclpCreateTemporaryDirectory -- + * + * Creates a temporary directory, possibly based on the supplied bits and + * pieces of template supplied in the arguments. + * + * Results: + * An object (refcount 0) containing the name of the newly-created + * directory, or NULL on failure. + * + * Side effects: + * Accesses the native filesystem. Makes a directory. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +TclpCreateTemporaryDirectory( + Tcl_Obj *dirObj, + Tcl_Obj *basenameObj) +{ + Tcl_DString template, tmp; + const char *string; + +#define DEFAULT_TEMP_DIR_PREFIX "tcl" + + /* + * Build the template in writable memory from the user-supplied pieces and + * some defaults. + */ + + if (dirObj) { + string = TclGetString(dirObj); + Tcl_UtfToExternalDString(NULL, string, dirObj->length, &template); + } else { + Tcl_DStringInit(&template); + Tcl_DStringAppend(&template, DefaultTempDir(), -1); /* INTL: native */ + } + + if (Tcl_DStringValue(&template)[Tcl_DStringLength(&template) - 1] != '/') { + TclDStringAppendLiteral(&template, "/"); + } + + if (basenameObj) { + string = TclGetString(basenameObj); + if (basenameObj->length) { + Tcl_UtfToExternalDString(NULL, string, basenameObj->length, &tmp); + TclDStringAppendDString(&template, &tmp); + Tcl_DStringFree(&tmp); + } else { + TclDStringAppendLiteral(&template, DEFAULT_TEMP_DIR_PREFIX); + } + } else { + TclDStringAppendLiteral(&template, DEFAULT_TEMP_DIR_PREFIX); + } + + TclDStringAppendLiteral(&template, "_XXXXXX"); + + /* + * Make the temporary directory. + */ + + if (mkdtemp(Tcl_DStringValue(&template)) == NULL) { + Tcl_DStringFree(&template); + return NULL; + } + + /* + * The template has been updated. Tell the caller what it was. + */ + + Tcl_ExternalToUtfDString(NULL, Tcl_DStringValue(&template), + Tcl_DStringLength(&template), &tmp); + Tcl_DStringFree(&template); + return TclDStringToObj(&tmp); +} #if defined(__CYGWIN__) static void StatError( Index: win/tclWinFCmd.c ================================================================== --- win/tclWinFCmd.c +++ win/tclWinFCmd.c @@ -1953,10 +1953,125 @@ } Tcl_IncrRefCount(resultPtr); return resultPtr; } + +/* + *---------------------------------------------------------------------- + * + * TclpCreateTemporaryDirectory -- + * + * Creates a temporary directory, possibly based on the supplied bits and + * pieces of template supplied in the arguments. + * + * Results: + * An object (refcount 0) containing the name of the newly-created + * directory, or NULL on failure. + * + * Side effects: + * Accesses the native filesystem. Makes a directory. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +TclpCreateTemporaryDirectory( + Tcl_Obj *dirObj, + Tcl_Obj *basenameObj) +{ + Tcl_DString base, name; /* Contains WCHARs */ + int baseLen; + DWORD error; + WCHAR tempBuf[MAX_PATH + 1]; + DWORD len = GetTempPathW(MAX_PATH, tempBuf); + + /* + * Build the path in writable memory from the user-supplied pieces and + * some defaults. First, the parent temporary directory. + */ + + if (dirObj) { + Tcl_GetString(dirObj); + if (dirObj->length < 1) { + goto useSystemTemp; + } + Tcl_WinUtfToTChar(Tcl_GetString(dirObj), -1, &base); + if (dirObj->bytes[dirObj->length - 1] != '\\') { + TclUtfToWCharDString("\\", -1, &base); + } + } else { + useSystemTemp: + Tcl_DStringInit(&base); + Tcl_DStringAppend(&base, (char *) tempBuf, len * sizeof(WCHAR)); + } + + /* + * Next, the base of the directory name. + */ + +#define DEFAULT_TEMP_DIR_PREFIX "tcl" +#define SUFFIX_LENGTH 8 + + if (basenameObj) { + Tcl_WinUtfToTChar(Tcl_GetString(basenameObj), -1, &name); + TclDStringAppendDString(&base, &name); + Tcl_DStringFree(&name); + } else { + TclUtfToWCharDString(DEFAULT_TEMP_DIR_PREFIX, -1, &base); + } + TclUtfToWCharDString("_", -1, &base); + + /* + * 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); + do { + char tempbuf[SUFFIX_LENGTH + 1]; + int i; + static const char randChars[] = + "QWERTYUIOPASDFGHJKLZXCVBNM1234567890"; + static const int numRandChars = sizeof(randChars) - 1; + + /* + * 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) + && (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 + * as a (clean) Tcl_Obj. + */ + + Tcl_WinTCharToUtf((LPCWSTR) Tcl_DStringValue(&base), -1, &name); + Tcl_DStringFree(&base); + return TclDStringToObj(&name); +} /* * Local Variables: * mode: c * c-basic-offset: 4