Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/tcl8.5.2/win/tclWinFCmd.c @ 68

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

added tcl to libs

File size: 55.8 KB
Line 
1/*
2 * tclWinFCmd.c
3 *
4 *      This file implements the Windows specific 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: tclWinFCmd.c,v 1.52 2006/08/29 00:36:57 coldstore Exp $
13 */
14
15#include "tclWinInt.h"
16
17/*
18 * The following constants specify the type of callback when
19 * TraverseWinTree() calls the traverseProc()
20 */
21
22#define DOTREE_PRED     1       /* pre-order directory  */
23#define DOTREE_POSTD    2       /* post-order directory */
24#define DOTREE_F        3       /* regular file */
25#define DOTREE_LINK     4       /* symbolic link */
26
27/*
28 * Callbacks for file attributes code.
29 */
30
31static int              GetWinFileAttributes(Tcl_Interp *interp, int objIndex,
32                            Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr);
33static int              GetWinFileLongName(Tcl_Interp *interp, int objIndex,
34                            Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr);
35static int              GetWinFileShortName(Tcl_Interp *interp, int objIndex,
36                            Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr);
37static int              SetWinFileAttributes(Tcl_Interp *interp, int objIndex,
38                            Tcl_Obj *fileName, Tcl_Obj *attributePtr);
39static int              CannotSetAttribute(Tcl_Interp *interp, int objIndex,
40                            Tcl_Obj *fileName, Tcl_Obj *attributePtr);
41
42/*
43 * Constants and variables necessary for file attributes subcommand.
44 */
45
46enum {
47    WIN_ARCHIVE_ATTRIBUTE,
48    WIN_HIDDEN_ATTRIBUTE,
49    WIN_LONGNAME_ATTRIBUTE,
50    WIN_READONLY_ATTRIBUTE,
51    WIN_SHORTNAME_ATTRIBUTE,
52    WIN_SYSTEM_ATTRIBUTE
53};
54
55static int attributeArray[] = {FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_HIDDEN,
56        0, FILE_ATTRIBUTE_READONLY, 0, FILE_ATTRIBUTE_SYSTEM};
57
58
59CONST char *tclpFileAttrStrings[] = {
60        "-archive", "-hidden", "-longname", "-readonly",
61        "-shortname", "-system", (char *) NULL
62};
63
64CONST TclFileAttrProcs tclpFileAttrProcs[] = {
65        {GetWinFileAttributes, SetWinFileAttributes},
66        {GetWinFileAttributes, SetWinFileAttributes},
67        {GetWinFileLongName, CannotSetAttribute},
68        {GetWinFileAttributes, SetWinFileAttributes},
69        {GetWinFileShortName, CannotSetAttribute},
70        {GetWinFileAttributes, SetWinFileAttributes}};
71
72#ifdef HAVE_NO_SEH
73
74/*
75 * Unlike Borland and Microsoft, we don't register exception handlers by
76 * pushing registration records onto the runtime stack. Instead, we register
77 * them by creating an EXCEPTION_REGISTRATION within the activation record.
78 */
79
80typedef struct EXCEPTION_REGISTRATION {
81    struct EXCEPTION_REGISTRATION *link;
82    EXCEPTION_DISPOSITION (*handler)(
83            struct _EXCEPTION_RECORD *, void *, struct _CONTEXT *, void *);
84    void *ebp;
85    void *esp;
86    int status;
87} EXCEPTION_REGISTRATION;
88
89#endif
90
91/*
92 * Prototype for the TraverseWinTree callback function.
93 */
94
95typedef int (TraversalProc)(CONST TCHAR *srcPtr, CONST TCHAR *dstPtr,
96        int type, Tcl_DString *errorPtr);
97
98/*
99 * Declarations for local functions defined in this file:
100 */
101
102static void             StatError(Tcl_Interp *interp, Tcl_Obj *fileName);
103static int              ConvertFileNameFormat(Tcl_Interp *interp,
104                            int objIndex, Tcl_Obj *fileName, int longShort,
105                            Tcl_Obj **attributePtrPtr);
106static int              DoCopyFile(CONST TCHAR *srcPtr, CONST TCHAR *dstPtr);
107static int              DoCreateDirectory(CONST TCHAR *pathPtr);
108static int              DoRemoveJustDirectory(CONST TCHAR *nativeSrc,
109                            int ignoreError, Tcl_DString *errorPtr);
110static int              DoRemoveDirectory(Tcl_DString *pathPtr, int recursive,
111                            Tcl_DString *errorPtr);
112static int              DoRenameFile(CONST TCHAR *nativeSrc,
113                            CONST TCHAR *dstPtr);
114static int              TraversalCopy(CONST TCHAR *srcPtr, CONST TCHAR *dstPtr,
115                            int type, Tcl_DString *errorPtr);
116static int              TraversalDelete(CONST TCHAR *srcPtr,
117                            CONST TCHAR *dstPtr, int type,
118                            Tcl_DString *errorPtr);
119static int              TraverseWinTree(TraversalProc *traverseProc,
120                            Tcl_DString *sourcePtr, Tcl_DString *dstPtr,
121                            Tcl_DString *errorPtr);
122
123/*
124 *---------------------------------------------------------------------------
125 *
126 * TclpObjRenameFile, DoRenameFile --
127 *
128 *      Changes the name of an existing file or directory, from src to dst.
129 *      If src and dst refer to the same file or directory, does nothing and
130 *      returns success. Otherwise if dst already exists, it will be deleted
131 *      and replaced by src subject to the following conditions:
132 *          If src is a directory, dst may be an empty directory.
133 *          If src is a file, dst may be a file.
134 *      In any other situation where dst already exists, the rename will fail.
135 *
136 * Results:
137 *      If the file or directory was successfully renamed, returns TCL_OK.
138 *      Otherwise the return value is TCL_ERROR and errno is set to indicate
139 *      the error. Some possible values for errno are:
140 *
141 *      ENAMETOOLONG: src or dst names are too long.
142 *      EACCES:     src or dst parent directory can't be read and/or written.
143 *      EEXIST:     dst is a non-empty directory.
144 *      EINVAL:     src is a root directory or dst is a subdirectory of src.
145 *      EISDIR:     dst is a directory, but src is not.
146 *      ENOENT:     src doesn't exist.  src or dst is "".
147 *      ENOTDIR:    src is a directory, but dst is not.
148 *      EXDEV:      src and dst are on different filesystems.
149 *
150 *      EACCES:     exists an open file already referring to src or dst.
151 *      EACCES:     src or dst specify the current working directory (NT).
152 *      EACCES:     src specifies a char device (nul:, com1:, etc.)
153 *      EEXIST:     dst specifies a char device (nul:, com1:, etc.) (NT)
154 *      EACCES:     dst specifies a char device (nul:, com1:, etc.) (95)
155 *
156 * Side effects:
157 *      The implementation supports cross-filesystem renames of files, but the
158 *      caller should be prepared to emulate cross-filesystem renames of
159 *      directories if errno is EXDEV.
160 *
161 *---------------------------------------------------------------------------
162 */
163
164int
165TclpObjRenameFile(
166    Tcl_Obj *srcPathPtr,
167    Tcl_Obj *destPathPtr)
168{
169    return DoRenameFile(Tcl_FSGetNativePath(srcPathPtr),
170            Tcl_FSGetNativePath(destPathPtr));
171}
172
173static int
174DoRenameFile(
175    CONST TCHAR *nativeSrc,     /* Pathname of file or dir to be renamed
176                                 * (native). */
177    CONST TCHAR *nativeDst)     /* New pathname for file or directory
178                                 * (native). */
179{
180#ifdef HAVE_NO_SEH
181    EXCEPTION_REGISTRATION registration;
182#endif
183    DWORD srcAttr, dstAttr;
184    int retval = -1;
185
186    /*
187     * The MoveFile API acts differently under Win95/98 and NT WRT NULL and
188     * "". Avoid passing these values.
189     */
190
191    if (nativeSrc == NULL || nativeSrc[0] == '\0' ||
192            nativeDst == NULL || nativeDst[0] == '\0') {
193        Tcl_SetErrno(ENOENT);
194        return TCL_ERROR;
195    }
196
197    /*
198     * The MoveFile API would throw an exception under NT if one of the
199     * arguments is a char block device.
200     */
201
202#ifndef HAVE_NO_SEH
203    __try {
204        if ((*tclWinProcs->moveFileProc)(nativeSrc, nativeDst) != FALSE) {
205            retval = TCL_OK;
206        }
207    } __except (EXCEPTION_EXECUTE_HANDLER) {}
208#else
209
210    /*
211     * Don't have SEH available, do things the hard way. Note that this needs
212     * to be one block of asm, to avoid stack imbalance; also, it is illegal
213     * for one asm block to contain a jump to another.
214     */
215
216    __asm__ __volatile__ (
217        /*
218         * Pick up params before messing with the stack.
219         */
220
221        "movl       %[nativeDst],   %%ebx"          "\n\t"
222        "movl       %[nativeSrc],   %%ecx"          "\n\t"
223
224        /*
225         * Construct an EXCEPTION_REGISTRATION to protect the call to
226         * MoveFile.
227         */
228
229        "leal       %[registration], %%edx"         "\n\t"
230        "movl       %%fs:0,         %%eax"          "\n\t"
231        "movl       %%eax,          0x0(%%edx)"     "\n\t" /* link */
232        "leal       1f,             %%eax"          "\n\t"
233        "movl       %%eax,          0x4(%%edx)"     "\n\t" /* handler */
234        "movl       %%ebp,          0x8(%%edx)"     "\n\t" /* ebp */
235        "movl       %%esp,          0xc(%%edx)"     "\n\t" /* esp */
236        "movl       $0,             0x10(%%edx)"    "\n\t" /* status */
237
238        /*
239         * Link the EXCEPTION_REGISTRATION on the chain.
240         */
241
242        "movl       %%edx,          %%fs:0"         "\n\t"
243
244        /*
245         * Call MoveFile(nativeSrc, nativeDst)
246         */
247
248        "pushl      %%ebx"                          "\n\t"
249        "pushl      %%ecx"                          "\n\t"
250        "movl       %[moveFile],    %%eax"          "\n\t"
251        "call       *%%eax"                         "\n\t"
252
253        /*
254         * Come here on normal exit. Recover the EXCEPTION_REGISTRATION and
255         * put the status return from MoveFile into it.
256         */
257
258        "movl       %%fs:0,         %%edx"          "\n\t"
259        "movl       %%eax,          0x10(%%edx)"    "\n\t"
260        "jmp        2f"                             "\n"
261
262        /*
263         * Come here on an exception. Recover the EXCEPTION_REGISTRATION
264         */
265
266        "1:"                                        "\t"
267        "movl       %%fs:0,         %%edx"          "\n\t"
268        "movl       0x8(%%edx),     %%edx"          "\n\t"
269
270        /*
271         * Come here however we exited. Restore context from the
272         * EXCEPTION_REGISTRATION in case the stack is unbalanced.
273         */
274
275        "2:"                                        "\t"
276        "movl       0xc(%%edx),     %%esp"          "\n\t"
277        "movl       0x8(%%edx),     %%ebp"          "\n\t"
278        "movl       0x0(%%edx),     %%eax"          "\n\t"
279        "movl       %%eax,          %%fs:0"         "\n\t"
280
281        :
282        /* No outputs */
283        :
284        [registration]  "m"     (registration),
285        [nativeDst]     "m"     (nativeDst),
286        [nativeSrc]     "m"     (nativeSrc),
287        [moveFile]      "r"     (tclWinProcs->moveFileProc)
288        :
289        "%eax", "%ebx", "%ecx", "%edx", "memory"
290        );
291    if (registration.status != FALSE) {
292        retval = TCL_OK;
293    }
294#endif
295
296    if (retval != -1) {
297        return retval;
298    }
299
300    TclWinConvertError(GetLastError());
301
302    srcAttr = (*tclWinProcs->getFileAttributesProc)(nativeSrc);
303    dstAttr = (*tclWinProcs->getFileAttributesProc)(nativeDst);
304    if (srcAttr == 0xffffffff) {
305        if ((*tclWinProcs->getFullPathNameProc)(nativeSrc, 0, NULL,
306                NULL) >= MAX_PATH) {
307            errno = ENAMETOOLONG;
308            return TCL_ERROR;
309        }
310        srcAttr = 0;
311    }
312    if (dstAttr == 0xffffffff) {
313        if ((*tclWinProcs->getFullPathNameProc)(nativeDst, 0, NULL,
314                NULL) >= MAX_PATH) {
315            errno = ENAMETOOLONG;
316            return TCL_ERROR;
317        }
318        dstAttr = 0;
319    }
320
321    if (errno == EBADF) {
322        errno = EACCES;
323        return TCL_ERROR;
324    }
325    if (errno == EACCES) {
326    decode:
327        if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) {
328            TCHAR *nativeSrcRest, *nativeDstRest;
329            CONST char **srcArgv, **dstArgv;
330            int size, srcArgc, dstArgc;
331            WCHAR nativeSrcPath[MAX_PATH];
332            WCHAR nativeDstPath[MAX_PATH];
333            Tcl_DString srcString, dstString;
334            CONST char *src, *dst;
335
336            size = (*tclWinProcs->getFullPathNameProc)(nativeSrc, MAX_PATH,
337                    nativeSrcPath, &nativeSrcRest);
338            if ((size == 0) || (size > MAX_PATH)) {
339                return TCL_ERROR;
340            }
341            size = (*tclWinProcs->getFullPathNameProc)(nativeDst, MAX_PATH,
342                    nativeDstPath, &nativeDstRest);
343            if ((size == 0) || (size > MAX_PATH)) {
344                return TCL_ERROR;
345            }
346            (*tclWinProcs->charLowerProc)((TCHAR *) nativeSrcPath);
347            (*tclWinProcs->charLowerProc)((TCHAR *) nativeDstPath);
348
349            src = Tcl_WinTCharToUtf((TCHAR *) nativeSrcPath, -1, &srcString);
350            dst = Tcl_WinTCharToUtf((TCHAR *) nativeDstPath, -1, &dstString);
351
352            /*
353             * Check whether the destination path is actually inside the
354             * source path. This is true if the prefix matches, and the next
355             * character is either end-of-string or a directory separator
356             */
357
358            if ((strncmp(src, dst, (size_t) Tcl_DStringLength(&srcString))==0)
359                    && (dst[Tcl_DStringLength(&srcString)] == '\\'
360                    || dst[Tcl_DStringLength(&srcString)] == '/'
361                    || dst[Tcl_DStringLength(&srcString)] == '\0')) {
362                /*
363                 * Trying to move a directory into itself.
364                 */
365
366                errno = EINVAL;
367                Tcl_DStringFree(&srcString);
368                Tcl_DStringFree(&dstString);
369                return TCL_ERROR;
370            }
371            Tcl_SplitPath(src, &srcArgc, &srcArgv);
372            Tcl_SplitPath(dst, &dstArgc, &dstArgv);
373            Tcl_DStringFree(&srcString);
374            Tcl_DStringFree(&dstString);
375
376            if (srcArgc == 1) {
377                /*
378                 * They are trying to move a root directory. Whether or not it
379                 * is across filesystems, this cannot be done.
380                 */
381
382                Tcl_SetErrno(EINVAL);
383            } else if ((srcArgc > 0) && (dstArgc > 0) &&
384                    (strcmp(srcArgv[0], dstArgv[0]) != 0)) {
385                /*
386                 * If src is a directory and dst filesystem != src filesystem,
387                 * errno should be EXDEV. It is very important to get this
388                 * behavior, so that the caller can respond to a cross
389                 * filesystem rename by simulating it with copy and delete.
390                 * The MoveFile system call already handles the case of moving
391                 * a file between filesystems.
392                 */
393
394                Tcl_SetErrno(EXDEV);
395            }
396
397            ckfree((char *) srcArgv);
398            ckfree((char *) dstArgv);
399        }
400
401        /*
402         * Other types of access failure is that dst is a read-only
403         * filesystem, that an open file referred to src or dest, or that src
404         * or dest specified the current working directory on the current
405         * filesystem. EACCES is returned for those cases.
406         */
407
408    } else if (Tcl_GetErrno() == EEXIST) {
409        /*
410         * Reports EEXIST any time the target already exists. If it makes
411         * sense, remove the old file and try renaming again.
412         */
413
414        if (srcAttr & FILE_ATTRIBUTE_DIRECTORY) {
415            if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) {
416                /*
417                 * Overwrite empty dst directory with src directory. The
418                 * following call will remove an empty directory. If it fails,
419                 * it's because it wasn't empty.
420                 */
421
422                if (DoRemoveJustDirectory(nativeDst, 0, NULL) == TCL_OK) {
423                    /*
424                     * Now that that empty directory is gone, we can try
425                     * renaming again. If that fails, we'll put this empty
426                     * directory back, for completeness.
427                     */
428
429                    if ((*tclWinProcs->moveFileProc)(nativeSrc,
430                            nativeDst) != FALSE) {
431                        return TCL_OK;
432                    }
433
434                    /*
435                     * Some new error has occurred. Don't know what it could
436                     * be, but report this one.
437                     */
438
439                    TclWinConvertError(GetLastError());
440                    (*tclWinProcs->createDirectoryProc)(nativeDst, NULL);
441                    (*tclWinProcs->setFileAttributesProc)(nativeDst, dstAttr);
442                    if (Tcl_GetErrno() == EACCES) {
443                        /*
444                         * Decode the EACCES to a more meaningful error.
445                         */
446
447                        goto decode;
448                    }
449                }
450            } else {    /* (dstAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */
451                Tcl_SetErrno(ENOTDIR);
452            }
453        } else {    /* (srcAttr & FILE_ATTRIBUTE_DIRECTORY) == 0 */
454            if (dstAttr & FILE_ATTRIBUTE_DIRECTORY) {
455                Tcl_SetErrno(EISDIR);
456            } else {
457                /*
458                 * Overwrite existing file by:
459                 *
460                 * 1. Rename existing file to temp name.
461                 * 2. Rename old file to new name.
462                 * 3. If success, delete temp file. If failure, put temp file
463                 *    back to old name.
464                 */
465
466                TCHAR *nativeRest, *nativeTmp, *nativePrefix;
467                int result, size;
468                WCHAR tempBuf[MAX_PATH];
469
470                size = (*tclWinProcs->getFullPathNameProc)(nativeDst, MAX_PATH,
471                        tempBuf, &nativeRest);
472                if ((size == 0) || (size > MAX_PATH) || (nativeRest == NULL)) {
473                    return TCL_ERROR;
474                }
475                nativeTmp = (TCHAR *) tempBuf;
476                ((char *) nativeRest)[0] = '\0';
477                ((char *) nativeRest)[1] = '\0';    /* In case it's Unicode. */
478
479                result = TCL_ERROR;
480                nativePrefix = (tclWinProcs->useWide)
481                        ? (TCHAR *) L"tclr" : (TCHAR *) "tclr";
482                if ((*tclWinProcs->getTempFileNameProc)(nativeTmp,
483                        nativePrefix, 0, tempBuf) != 0) {
484                    /*
485                     * Strictly speaking, need the following DeleteFile and
486                     * MoveFile to be joined as an atomic operation so no
487                     * other app comes along in the meantime and creates the
488                     * same temp file.
489                     */
490
491                    nativeTmp = (TCHAR *) tempBuf;
492                    (*tclWinProcs->deleteFileProc)(nativeTmp);
493                    if ((*tclWinProcs->moveFileProc)(nativeDst,
494                            nativeTmp) != FALSE) {
495                        if ((*tclWinProcs->moveFileProc)(nativeSrc,
496                                nativeDst) != FALSE) {
497                            (*tclWinProcs->setFileAttributesProc)(nativeTmp,
498                                    FILE_ATTRIBUTE_NORMAL);
499                            (*tclWinProcs->deleteFileProc)(nativeTmp);
500                            return TCL_OK;
501                        } else {
502                            (*tclWinProcs->deleteFileProc)(nativeDst);
503                            (*tclWinProcs->moveFileProc)(nativeTmp, nativeDst);
504                        }
505                    }
506
507                    /*
508                     * Can't backup dst file or move src file. Return that
509                     * error. Could happen if an open file refers to dst.
510                     */
511
512                    TclWinConvertError(GetLastError());
513                    if (Tcl_GetErrno() == EACCES) {
514                        /*
515                         * Decode the EACCES to a more meaningful error.
516                         */
517
518                        goto decode;
519                    }
520                }
521                return result;
522            }
523        }
524    }
525    return TCL_ERROR;
526}
527
528/*
529 *---------------------------------------------------------------------------
530 *
531 * TclpObjCopyFile, DoCopyFile --
532 *
533 *      Copy a single file (not a directory). If dst already exists and is not
534 *      a directory, it is removed.
535 *
536 * Results:
537 *      If the file was successfully copied, returns TCL_OK. Otherwise the
538 *      return value is TCL_ERROR and errno is set to indicate the error.
539 *      Some possible values for errno are:
540 *
541 *      EACCES:     src or dst parent directory can't be read and/or written.
542 *      EISDIR:     src or dst is a directory.
543 *      ENOENT:     src doesn't exist.  src or dst is "".
544 *
545 *      EACCES:     exists an open file already referring to dst (95).
546 *      EACCES:     src specifies a char device (nul:, com1:, etc.) (NT)
547 *      ENOENT:     src specifies a char device (nul:, com1:, etc.) (95)
548 *
549 * Side effects:
550 *      It is not an error to copy to a char device.
551 *
552 *---------------------------------------------------------------------------
553 */
554
555int
556TclpObjCopyFile(
557    Tcl_Obj *srcPathPtr,
558    Tcl_Obj *destPathPtr)
559{
560    return DoCopyFile(Tcl_FSGetNativePath(srcPathPtr),
561            Tcl_FSGetNativePath(destPathPtr));
562}
563
564static int
565DoCopyFile(
566    CONST TCHAR *nativeSrc,     /* Pathname of file to be copied (native). */
567    CONST TCHAR *nativeDst)     /* Pathname of file to copy to (native). */
568{
569#ifdef HAVE_NO_SEH
570    EXCEPTION_REGISTRATION registration;
571#endif
572    int retval = -1;
573
574    /*
575     * The CopyFile API acts differently under Win95/98 and NT WRT NULL and
576     * "". Avoid passing these values.
577     */
578
579    if (nativeSrc == NULL || nativeSrc[0] == '\0' ||
580            nativeDst == NULL || nativeDst[0] == '\0') {
581        Tcl_SetErrno(ENOENT);
582        return TCL_ERROR;
583    }
584
585    /*
586     * The CopyFile API would throw an exception under NT if one of the
587     * arguments is a char block device.
588     */
589
590#ifndef HAVE_NO_SEH
591    __try {
592        if ((*tclWinProcs->copyFileProc)(nativeSrc, nativeDst, 0) != FALSE) {
593            retval = TCL_OK;
594        }
595    } __except (EXCEPTION_EXECUTE_HANDLER) {}
596#else
597
598    /*
599     * Don't have SEH available, do things the hard way. Note that this needs
600     * to be one block of asm, to avoid stack imbalance; also, it is illegal
601     * for one asm block to contain a jump to another.
602     */
603
604    __asm__ __volatile__ (
605
606        /*
607         * Pick up parameters before messing with the stack
608         */
609
610        "movl       %[nativeDst],   %%ebx"          "\n\t"
611        "movl       %[nativeSrc],   %%ecx"          "\n\t"
612
613        /*
614         * Construct an EXCEPTION_REGISTRATION to protect the call to
615         * CopyFile.
616         */
617
618        "leal       %[registration], %%edx"         "\n\t"
619        "movl       %%fs:0,         %%eax"          "\n\t"
620        "movl       %%eax,          0x0(%%edx)"     "\n\t" /* link */
621        "leal       1f,             %%eax"          "\n\t"
622        "movl       %%eax,          0x4(%%edx)"     "\n\t" /* handler */
623        "movl       %%ebp,          0x8(%%edx)"     "\n\t" /* ebp */
624        "movl       %%esp,          0xc(%%edx)"     "\n\t" /* esp */
625        "movl       $0,             0x10(%%edx)"    "\n\t" /* status */
626
627        /*
628         * Link the EXCEPTION_REGISTRATION on the chain.
629         */
630
631        "movl       %%edx,          %%fs:0"         "\n\t"
632
633        /*
634         * Call CopyFile(nativeSrc, nativeDst, 0)
635         */
636
637        "movl       %[copyFile],    %%eax"          "\n\t"
638        "pushl      $0"                             "\n\t"
639        "pushl      %%ebx"                          "\n\t"
640        "pushl      %%ecx"                          "\n\t"
641        "call       *%%eax"                         "\n\t"
642
643        /*
644         * Come here on normal exit. Recover the EXCEPTION_REGISTRATION and
645         * put the status return from CopyFile into it.
646         */
647
648        "movl       %%fs:0,         %%edx"          "\n\t"
649        "movl       %%eax,          0x10(%%edx)"    "\n\t"
650        "jmp        2f"                             "\n"
651
652        /*
653         * Come here on an exception. Recover the EXCEPTION_REGISTRATION
654         */
655
656        "1:"                                        "\t"
657        "movl       %%fs:0,         %%edx"          "\n\t"
658        "movl       0x8(%%edx),     %%edx"          "\n\t"
659
660        /*
661         * Come here however we exited. Restore context from the
662         * EXCEPTION_REGISTRATION in case the stack is unbalanced.
663         */
664
665        "2:"                                        "\t"
666        "movl       0xc(%%edx),     %%esp"          "\n\t"
667        "movl       0x8(%%edx),     %%ebp"          "\n\t"
668        "movl       0x0(%%edx),     %%eax"          "\n\t"
669        "movl       %%eax,          %%fs:0"         "\n\t"
670
671        :
672        /* No outputs */
673        :
674        [registration]  "m"     (registration),
675        [nativeDst]     "m"     (nativeDst),
676        [nativeSrc]     "m"     (nativeSrc),
677        [copyFile]      "r"     (tclWinProcs->copyFileProc)
678        :
679        "%eax", "%ebx", "%ecx", "%edx", "memory"
680        );
681    if (registration.status != FALSE) {
682        retval = TCL_OK;
683    }
684#endif
685
686    if (retval != -1) {
687        return retval;
688    }
689
690    TclWinConvertError(GetLastError());
691    if (Tcl_GetErrno() == EBADF) {
692        Tcl_SetErrno(EACCES);
693        return TCL_ERROR;
694    }
695    if (Tcl_GetErrno() == EACCES) {
696        DWORD srcAttr, dstAttr;
697
698        srcAttr = (*tclWinProcs->getFileAttributesProc)(nativeSrc);
699        dstAttr = (*tclWinProcs->getFileAttributesProc)(nativeDst);
700        if (srcAttr != 0xffffffff) {
701            if (dstAttr == 0xffffffff) {
702                dstAttr = 0;
703            }
704            if ((srcAttr & FILE_ATTRIBUTE_DIRECTORY) ||
705                    (dstAttr & FILE_ATTRIBUTE_DIRECTORY)) {
706                if (srcAttr & FILE_ATTRIBUTE_REPARSE_POINT) {
707                    /* Source is a symbolic link -- copy it */
708                    if (TclWinSymLinkCopyDirectory(nativeSrc, nativeDst)==0) {
709                        return TCL_OK;
710                    }
711                }
712                Tcl_SetErrno(EISDIR);
713            }
714            if (dstAttr & FILE_ATTRIBUTE_READONLY) {
715                (*tclWinProcs->setFileAttributesProc)(nativeDst,
716                        dstAttr & ~((DWORD)FILE_ATTRIBUTE_READONLY));
717                if ((*tclWinProcs->copyFileProc)(nativeSrc, nativeDst,
718                        0) != FALSE) {
719                    return TCL_OK;
720                }
721
722                /*
723                 * Still can't copy onto dst. Return that error, and restore
724                 * attributes of dst.
725                 */
726
727                TclWinConvertError(GetLastError());
728                (*tclWinProcs->setFileAttributesProc)(nativeDst, dstAttr);
729            }
730        }
731    }
732    return TCL_ERROR;
733}
734
735/*
736 *---------------------------------------------------------------------------
737 *
738 * TclpObjDeleteFile, TclpDeleteFile --
739 *
740 *      Removes a single file (not a directory).
741 *
742 * Results:
743 *      If the file was successfully deleted, returns TCL_OK. Otherwise the
744 *      return value is TCL_ERROR and errno is set to indicate the error.
745 *      Some possible values for errno are:
746 *
747 *      EACCES:     a parent directory can't be read and/or written.
748 *      EISDIR:     path is a directory.
749 *      ENOENT:     path doesn't exist or is "".
750 *
751 *      EACCES:     exists an open file already referring to path.
752 *      EACCES:     path is a char device (nul:, com1:, etc.)
753 *
754 * Side effects:
755 *      The file is deleted, even if it is read-only.
756 *
757 *---------------------------------------------------------------------------
758 */
759
760int
761TclpObjDeleteFile(
762    Tcl_Obj *pathPtr)
763{
764    return TclpDeleteFile(Tcl_FSGetNativePath(pathPtr));
765}
766
767int
768TclpDeleteFile(
769    CONST TCHAR *nativePath)    /* Pathname of file to be removed (native). */
770{
771    DWORD attr;
772
773    /*
774     * The DeleteFile API acts differently under Win95/98 and NT WRT NULL and
775     * "". Avoid passing these values.
776     */
777
778    if (nativePath == NULL || nativePath[0] == '\0') {
779        Tcl_SetErrno(ENOENT);
780        return TCL_ERROR;
781    }
782
783    if ((*tclWinProcs->deleteFileProc)(nativePath) != FALSE) {
784        return TCL_OK;
785    }
786    TclWinConvertError(GetLastError());
787
788    if (Tcl_GetErrno() == EACCES) {
789        attr = (*tclWinProcs->getFileAttributesProc)(nativePath);
790        if (attr != 0xffffffff) {
791            if (attr & FILE_ATTRIBUTE_DIRECTORY) {
792                if (attr & FILE_ATTRIBUTE_REPARSE_POINT) {
793                    /*
794                     * It is a symbolic link - remove it.
795                     */
796                    if (TclWinSymLinkDelete(nativePath, 0) == 0) {
797                        return TCL_OK;
798                    }
799                }
800
801                /*
802                 * If we fall through here, it is a directory.
803                 *
804                 * Windows NT reports removing a directory as EACCES instead
805                 * of EISDIR.
806                 */
807
808                Tcl_SetErrno(EISDIR);
809            } else if (attr & FILE_ATTRIBUTE_READONLY) {
810                int res = (*tclWinProcs->setFileAttributesProc)(nativePath,
811                        attr & ~((DWORD)FILE_ATTRIBUTE_READONLY));
812
813                if ((res != 0) && ((*tclWinProcs->deleteFileProc)(nativePath)
814                        != FALSE)) {
815                    return TCL_OK;
816                }
817                TclWinConvertError(GetLastError());
818                if (res != 0) {
819                    (*tclWinProcs->setFileAttributesProc)(nativePath, attr);
820                }
821            }
822        }
823    } else if (Tcl_GetErrno() == ENOENT) {
824        attr = (*tclWinProcs->getFileAttributesProc)(nativePath);
825        if (attr != 0xffffffff) {
826            if (attr & FILE_ATTRIBUTE_DIRECTORY) {
827                /*
828                 * Windows 95 reports removing a directory as ENOENT instead
829                 * of EISDIR.
830                 */
831
832                Tcl_SetErrno(EISDIR);
833            }
834        }
835    } else if (Tcl_GetErrno() == EINVAL) {
836        /*
837         * Windows NT reports removing a char device as EINVAL instead of
838         * EACCES.
839         */
840
841        Tcl_SetErrno(EACCES);
842    }
843
844    return TCL_ERROR;
845}
846
847/*
848 *---------------------------------------------------------------------------
849 *
850 * TclpObjCreateDirectory --
851 *
852 *      Creates the specified directory. All parent directories of the
853 *      specified directory must already exist. The directory is automatically
854 *      created with permissions so that user can access the new directory and
855 *      create new files or subdirectories in it.
856 *
857 * Results:
858 *      If the directory was successfully created, returns TCL_OK. Otherwise
859 *      the return value is TCL_ERROR and errno is set to indicate the error.
860 *      Some possible values for errno are:
861 *
862 *      EACCES:     a parent directory can't be read and/or written.
863 *      EEXIST:     path already exists.
864 *      ENOENT:     a parent directory doesn't exist.
865 *
866 * Side effects:
867 *      A directory is created.
868 *
869 *---------------------------------------------------------------------------
870 */
871
872int
873TclpObjCreateDirectory(
874    Tcl_Obj *pathPtr)
875{
876    return DoCreateDirectory(Tcl_FSGetNativePath(pathPtr));
877}
878
879static int
880DoCreateDirectory(
881    CONST TCHAR *nativePath)    /* Pathname of directory to create (native). */
882{
883    DWORD error;
884    if ((*tclWinProcs->createDirectoryProc)(nativePath, NULL) == 0) {
885        error = GetLastError();
886        TclWinConvertError(error);
887        return TCL_ERROR;
888    }
889    return TCL_OK;
890}
891
892/*
893 *---------------------------------------------------------------------------
894 *
895 * TclpObjCopyDirectory --
896 *
897 *      Recursively copies a directory. The target directory dst must not
898 *      already exist. Note that this function does not merge two directory
899 *      hierarchies, even if the target directory is an an empty directory.
900 *
901 * Results:
902 *      If the directory was successfully copied, returns TCL_OK. Otherwise
903 *      the return value is TCL_ERROR, errno is set to indicate the error, and
904 *      the pathname of the file that caused the error is stored in errorPtr.
905 *      See TclpCreateDirectory and TclpCopyFile for a description of possible
906 *      values for errno.
907 *
908 * Side effects:
909 *      An exact copy of the directory hierarchy src will be created with the
910 *      name dst. If an error occurs, the error will be returned immediately,
911 *      and remaining files will not be processed.
912 *
913 *---------------------------------------------------------------------------
914 */
915
916int
917TclpObjCopyDirectory(
918    Tcl_Obj *srcPathPtr,
919    Tcl_Obj *destPathPtr,
920    Tcl_Obj **errorPtr)
921{
922    Tcl_DString ds;
923    Tcl_DString srcString, dstString;
924    Tcl_Obj *normSrcPtr, *normDestPtr;
925    int ret;
926
927    normSrcPtr = Tcl_FSGetNormalizedPath(NULL,srcPathPtr);
928    normDestPtr = Tcl_FSGetNormalizedPath(NULL,destPathPtr);
929    if ((normSrcPtr == NULL) || (normDestPtr == NULL)) {
930        return TCL_ERROR;
931    }
932
933    Tcl_WinUtfToTChar(Tcl_GetString(normSrcPtr), -1, &srcString);
934    Tcl_WinUtfToTChar(Tcl_GetString(normDestPtr), -1, &dstString);
935
936    ret = TraverseWinTree(TraversalCopy, &srcString, &dstString, &ds);
937
938    Tcl_DStringFree(&srcString);
939    Tcl_DStringFree(&dstString);
940
941    if (ret != TCL_OK) {
942        if (!strcmp(Tcl_DStringValue(&ds), TclGetString(normSrcPtr))) {
943            *errorPtr = srcPathPtr;
944        } else if (!strcmp(Tcl_DStringValue(&ds), TclGetString(normDestPtr))) {
945            *errorPtr = destPathPtr;
946        } else {
947            *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
948        }
949        Tcl_DStringFree(&ds);
950        Tcl_IncrRefCount(*errorPtr);
951    }
952    return ret;
953}
954
955/*
956 *----------------------------------------------------------------------
957 *
958 * TclpObjRemoveDirectory, DoRemoveDirectory --
959 *
960 *      Removes directory (and its contents, if the recursive flag is set).
961 *
962 * Results:
963 *      If the directory was successfully removed, returns TCL_OK. Otherwise
964 *      the return value is TCL_ERROR, errno is set to indicate the error, and
965 *      the pathname of the file that caused the error is stored in errorPtr.
966 *      Some possible values for errno are:
967 *
968 *      EACCES:     path directory can't be read and/or written.
969 *      EEXIST:     path is a non-empty directory.
970 *      EINVAL:     path is root directory or current directory.
971 *      ENOENT:     path doesn't exist or is "".
972 *      ENOTDIR:    path is not a directory.
973 *
974 *      EACCES:     path is a char device (nul:, com1:, etc.) (95)
975 *      EINVAL:     path is a char device (nul:, com1:, etc.) (NT)
976 *
977 * Side effects:
978 *      Directory removed. If an error occurs, the error will be returned
979 *      immediately, and remaining files will not be deleted.
980 *
981 *----------------------------------------------------------------------
982 */
983
984int
985TclpObjRemoveDirectory(
986    Tcl_Obj *pathPtr,
987    int recursive,
988    Tcl_Obj **errorPtr)
989{
990    Tcl_DString ds;
991    Tcl_Obj *normPtr = NULL;
992    int ret;
993
994    if (recursive) {
995        /*
996         * In the recursive case, the string rep is used to construct a
997         * Tcl_DString which may be used extensively, so we can't optimize
998         * this case easily.
999         */
1000
1001        Tcl_DString native;
1002        normPtr = Tcl_FSGetNormalizedPath(NULL, pathPtr);
1003        if (normPtr == NULL) {
1004            return TCL_ERROR;
1005        }
1006        Tcl_WinUtfToTChar(Tcl_GetString(normPtr), -1, &native);
1007        ret = DoRemoveDirectory(&native, recursive, &ds);
1008        Tcl_DStringFree(&native);
1009    } else {
1010        ret = DoRemoveJustDirectory(Tcl_FSGetNativePath(pathPtr), 0, &ds);
1011    }
1012
1013    if (ret != TCL_OK) {
1014        int len = Tcl_DStringLength(&ds);
1015        if (len > 0) {
1016            if (normPtr != NULL &&
1017                    !strcmp(Tcl_DStringValue(&ds), TclGetString(normPtr))) {
1018                *errorPtr = pathPtr;
1019            } else {
1020                *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
1021            }
1022            Tcl_IncrRefCount(*errorPtr);
1023        }
1024        Tcl_DStringFree(&ds);
1025    }
1026
1027    return ret;
1028}
1029
1030static int
1031DoRemoveJustDirectory(
1032    CONST TCHAR *nativePath,    /* Pathname of directory to be removed
1033                                 * (native). */
1034    int ignoreError,            /* If non-zero, don't initialize the errorPtr
1035                                 * under some circumstances on return. */
1036    Tcl_DString *errorPtr)      /* If non-NULL, uninitialized or free DString
1037                                 * filled with UTF-8 name of file causing
1038                                 * error. */
1039{
1040    DWORD attr;
1041
1042    /*
1043     * The RemoveDirectory API acts differently under Win95/98 and NT WRT NULL
1044     * and "". Avoid passing these values.
1045     */
1046
1047    if (nativePath == NULL || nativePath[0] == '\0') {
1048        Tcl_SetErrno(ENOENT);
1049        goto end;
1050    }
1051
1052    attr = (*tclWinProcs->getFileAttributesProc)(nativePath);
1053
1054    if (attr & FILE_ATTRIBUTE_REPARSE_POINT) {
1055        /*
1056         * It is a symbolic link - remove it.
1057         */
1058        if (TclWinSymLinkDelete(nativePath, 0) == 0) {
1059            return TCL_OK;
1060        }
1061    } else {
1062        /*
1063         * Ordinary directory.
1064         */
1065
1066        if ((*tclWinProcs->removeDirectoryProc)(nativePath) != FALSE) {
1067            return TCL_OK;
1068        }
1069    }
1070
1071    TclWinConvertError(GetLastError());
1072
1073    if (Tcl_GetErrno() == EACCES) {
1074        attr = (*tclWinProcs->getFileAttributesProc)(nativePath);
1075        if (attr != 0xffffffff) {
1076            if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
1077                /*
1078                 * Windows 95 reports calling RemoveDirectory on a file as an
1079                 * EACCES, not an ENOTDIR.
1080                 */
1081
1082                Tcl_SetErrno(ENOTDIR);
1083                goto end;
1084            }
1085
1086            if (attr & FILE_ATTRIBUTE_REPARSE_POINT) {
1087                /*
1088                 * It is a symbolic link - remove it.
1089                 */
1090
1091                if (TclWinSymLinkDelete(nativePath, 1) != 0) {
1092                    goto end;
1093                }
1094            }
1095
1096            if (attr & FILE_ATTRIBUTE_READONLY) {
1097                attr &= ~FILE_ATTRIBUTE_READONLY;
1098                if ((*tclWinProcs->setFileAttributesProc)(nativePath,
1099                        attr) == FALSE) {
1100                    goto end;
1101                }
1102                if ((*tclWinProcs->removeDirectoryProc)(nativePath) != FALSE) {
1103                    return TCL_OK;
1104                }
1105                TclWinConvertError(GetLastError());
1106                (*tclWinProcs->setFileAttributesProc)(nativePath,
1107                        attr | FILE_ATTRIBUTE_READONLY);
1108            }
1109
1110            /*
1111             * Windows 95 and Win32s report removing a non-empty directory as
1112             * EACCES, not EEXIST. If the directory is not empty, change errno
1113             * so caller knows what's going on.
1114             */
1115
1116            if (TclWinGetPlatformId() != VER_PLATFORM_WIN32_NT) {
1117                CONST char *path, *find;
1118                HANDLE handle;
1119                WIN32_FIND_DATAA data;
1120                Tcl_DString buffer;
1121                int len;
1122
1123                path = (CONST char *) nativePath;
1124
1125                Tcl_DStringInit(&buffer);
1126                len = strlen(path);
1127                find = Tcl_DStringAppend(&buffer, path, len);
1128                if ((len > 0) && (find[len - 1] != '\\')) {
1129                    Tcl_DStringAppend(&buffer, "\\", 1);
1130                }
1131                find = Tcl_DStringAppend(&buffer, "*.*", 3);
1132                handle = FindFirstFileA(find, &data);
1133                if (handle != INVALID_HANDLE_VALUE) {
1134                    while (1) {
1135                        if ((strcmp(data.cFileName, ".") != 0)
1136                                && (strcmp(data.cFileName, "..") != 0)) {
1137                            /*
1138                             * Found something in this directory.
1139                             */
1140
1141                            Tcl_SetErrno(EEXIST);
1142                            break;
1143                        }
1144                        if (FindNextFileA(handle, &data) == FALSE) {
1145                            break;
1146                        }
1147                    }
1148                    FindClose(handle);
1149                }
1150                Tcl_DStringFree(&buffer);
1151            }
1152        }
1153    }
1154
1155    if (Tcl_GetErrno() == ENOTEMPTY) {
1156        /*
1157         * The caller depends on EEXIST to signify that the directory is not
1158         * empty, not ENOTEMPTY.
1159         */
1160
1161        Tcl_SetErrno(EEXIST);
1162    }
1163
1164    if ((ignoreError != 0) && (Tcl_GetErrno() == EEXIST)) {
1165        /*
1166         * If we're being recursive, this error may actually be ok, so we
1167         * don't want to initialise the errorPtr yet.
1168         */
1169        return TCL_ERROR;
1170    }
1171
1172  end:
1173    if (errorPtr != NULL) {
1174        Tcl_WinTCharToUtf(nativePath, -1, errorPtr);
1175    }
1176    return TCL_ERROR;
1177
1178}
1179
1180static int
1181DoRemoveDirectory(
1182    Tcl_DString *pathPtr,       /* Pathname of directory to be removed
1183                                 * (native). */
1184    int recursive,              /* If non-zero, removes directories that are
1185                                 * nonempty. Otherwise, will only remove empty
1186                                 * directories. */
1187    Tcl_DString *errorPtr)      /* If non-NULL, uninitialized or free DString
1188                                 * filled with UTF-8 name of file causing
1189                                 * error. */
1190{
1191    int res = DoRemoveJustDirectory(Tcl_DStringValue(pathPtr), recursive,
1192            errorPtr);
1193
1194    if ((res == TCL_ERROR) && (recursive != 0) && (Tcl_GetErrno() == EEXIST)) {
1195        /*
1196         * The directory is nonempty, but the recursive flag has been
1197         * specified, so we recursively remove all the files in the directory.
1198         */
1199
1200        return TraverseWinTree(TraversalDelete, pathPtr, NULL, errorPtr);
1201    } else {
1202        return res;
1203    }
1204}
1205
1206/*
1207 *---------------------------------------------------------------------------
1208 *
1209 * TraverseWinTree --
1210 *
1211 *      Traverse directory tree specified by sourcePtr, calling the function
1212 *      traverseProc for each file and directory encountered. If destPtr is
1213 *      non-null, each of name in the sourcePtr directory is appended to the
1214 *      directory specified by destPtr and passed as the second argument to
1215 *      traverseProc().
1216 *
1217 * Results:
1218 *      Standard Tcl result.
1219 *
1220 * Side effects:
1221 *      None caused by TraverseWinTree, however the user specified
1222 *      traverseProc() may change state. If an error occurs, the error will be
1223 *      returned immediately, and remaining files will not be processed.
1224 *
1225 *---------------------------------------------------------------------------
1226 */
1227
1228static int
1229TraverseWinTree(
1230    TraversalProc *traverseProc,/* Function to call for every file and
1231                                 * directory in source hierarchy. */
1232    Tcl_DString *sourcePtr,     /* Pathname of source directory to be
1233                                 * traversed (native). */
1234    Tcl_DString *targetPtr,     /* Pathname of directory to traverse in
1235                                 * parallel with source directory (native),
1236                                 * may be NULL. */
1237    Tcl_DString *errorPtr)      /* If non-NULL, uninitialized or free DString
1238                                 * filled with UTF-8 name of file causing
1239                                 * error. */
1240{
1241    DWORD sourceAttr;
1242    TCHAR *nativeSource, *nativeTarget, *nativeErrfile;
1243    int result, found, sourceLen, targetLen = 0, oldSourceLen, oldTargetLen;
1244    HANDLE handle;
1245    WIN32_FIND_DATAT data;
1246
1247    nativeErrfile = NULL;
1248    result = TCL_OK;
1249    oldTargetLen = 0;           /* lint. */
1250
1251    nativeSource = (TCHAR *) Tcl_DStringValue(sourcePtr);
1252    nativeTarget = (TCHAR *)
1253            (targetPtr == NULL ? NULL : Tcl_DStringValue(targetPtr));
1254
1255    oldSourceLen = Tcl_DStringLength(sourcePtr);
1256    sourceAttr = (*tclWinProcs->getFileAttributesProc)(nativeSource);
1257    if (sourceAttr == 0xffffffff) {
1258        nativeErrfile = nativeSource;
1259        goto end;
1260    }
1261
1262    if (sourceAttr & FILE_ATTRIBUTE_REPARSE_POINT) {
1263        /*
1264         * Process the symbolic link
1265         */
1266
1267        return (*traverseProc)(nativeSource, nativeTarget, DOTREE_LINK,
1268                errorPtr);
1269    }
1270
1271    if ((sourceAttr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
1272        /*
1273         * Process the regular file
1274         */
1275
1276        return (*traverseProc)(nativeSource, nativeTarget, DOTREE_F, errorPtr);
1277    }
1278
1279    if (tclWinProcs->useWide) {
1280        Tcl_DStringAppend(sourcePtr, (char *) L"\\*.*", 4 * sizeof(WCHAR) + 1);
1281        Tcl_DStringSetLength(sourcePtr, Tcl_DStringLength(sourcePtr) - 1);
1282    } else {
1283        Tcl_DStringAppend(sourcePtr, "\\*.*", 4);
1284    }
1285
1286    nativeSource = (TCHAR *) Tcl_DStringValue(sourcePtr);
1287    handle = (*tclWinProcs->findFirstFileProc)(nativeSource, &data);
1288    if (handle == INVALID_HANDLE_VALUE) {
1289        /*
1290         * Can't read directory.
1291         */
1292
1293        TclWinConvertError(GetLastError());
1294        nativeErrfile = nativeSource;
1295        goto end;
1296    }
1297
1298    nativeSource[oldSourceLen + 1] = '\0';
1299    Tcl_DStringSetLength(sourcePtr, oldSourceLen);
1300    result = (*traverseProc)(nativeSource, nativeTarget, DOTREE_PRED,
1301            errorPtr);
1302    if (result != TCL_OK) {
1303        FindClose(handle);
1304        return result;
1305    }
1306
1307    sourceLen = oldSourceLen;
1308
1309    if (tclWinProcs->useWide) {
1310        sourceLen += sizeof(WCHAR);
1311        Tcl_DStringAppend(sourcePtr, (char *) L"\\", sizeof(WCHAR) + 1);
1312        Tcl_DStringSetLength(sourcePtr, sourceLen);
1313    } else {
1314        sourceLen += 1;
1315        Tcl_DStringAppend(sourcePtr, "\\", 1);
1316    }
1317    if (targetPtr != NULL) {
1318        oldTargetLen = Tcl_DStringLength(targetPtr);
1319
1320        targetLen = oldTargetLen;
1321        if (tclWinProcs->useWide) {
1322            targetLen += sizeof(WCHAR);
1323            Tcl_DStringAppend(targetPtr, (char *) L"\\", sizeof(WCHAR) + 1);
1324            Tcl_DStringSetLength(targetPtr, targetLen);
1325        } else {
1326            targetLen += 1;
1327            Tcl_DStringAppend(targetPtr, "\\", 1);
1328        }
1329    }
1330
1331    found = 1;
1332    for (; found; found = (*tclWinProcs->findNextFileProc)(handle, &data)) {
1333        TCHAR *nativeName;
1334        int len;
1335
1336        if (tclWinProcs->useWide) {
1337            WCHAR *wp;
1338
1339            wp = data.w.cFileName;
1340            if (*wp == '.') {
1341                wp++;
1342                if (*wp == '.') {
1343                    wp++;
1344                }
1345                if (*wp == '\0') {
1346                    continue;
1347                }
1348            }
1349            nativeName = (TCHAR *) data.w.cFileName;
1350            len = wcslen(data.w.cFileName) * sizeof(WCHAR);
1351        } else {
1352            if ((strcmp(data.a.cFileName, ".") == 0)
1353                    || (strcmp(data.a.cFileName, "..") == 0)) {
1354                continue;
1355            }
1356            nativeName = (TCHAR *) data.a.cFileName;
1357            len = strlen(data.a.cFileName);
1358        }
1359
1360        /*
1361         * Append name after slash, and recurse on the file.
1362         */
1363
1364        Tcl_DStringAppend(sourcePtr, (char *) nativeName, len + 1);
1365        Tcl_DStringSetLength(sourcePtr, Tcl_DStringLength(sourcePtr) - 1);
1366        if (targetPtr != NULL) {
1367            Tcl_DStringAppend(targetPtr, (char *) nativeName, len + 1);
1368            Tcl_DStringSetLength(targetPtr, Tcl_DStringLength(targetPtr) - 1);
1369        }
1370        result = TraverseWinTree(traverseProc, sourcePtr, targetPtr,
1371                errorPtr);
1372        if (result != TCL_OK) {
1373            break;
1374        }
1375
1376        /*
1377         * Remove name after slash.
1378         */
1379
1380        Tcl_DStringSetLength(sourcePtr, sourceLen);
1381        if (targetPtr != NULL) {
1382            Tcl_DStringSetLength(targetPtr, targetLen);
1383        }
1384    }
1385    FindClose(handle);
1386
1387    /*
1388     * Strip off the trailing slash we added.
1389     */
1390
1391    Tcl_DStringSetLength(sourcePtr, oldSourceLen + 1);
1392    Tcl_DStringSetLength(sourcePtr, oldSourceLen);
1393    if (targetPtr != NULL) {
1394        Tcl_DStringSetLength(targetPtr, oldTargetLen + 1);
1395        Tcl_DStringSetLength(targetPtr, oldTargetLen);
1396    }
1397    if (result == TCL_OK) {
1398        /*
1399         * Call traverseProc() on a directory after visiting all the
1400         * files in that directory.
1401         */
1402
1403        result = (*traverseProc)(Tcl_DStringValue(sourcePtr),
1404                (targetPtr == NULL ? NULL : Tcl_DStringValue(targetPtr)),
1405                DOTREE_POSTD, errorPtr);
1406    }
1407
1408  end:
1409    if (nativeErrfile != NULL) {
1410        TclWinConvertError(GetLastError());
1411        if (errorPtr != NULL) {
1412            Tcl_WinTCharToUtf(nativeErrfile, -1, errorPtr);
1413        }
1414        result = TCL_ERROR;
1415    }
1416
1417    return result;
1418}
1419
1420/*
1421 *----------------------------------------------------------------------
1422 *
1423 * TraversalCopy
1424 *
1425 *      Called from TraverseUnixTree in order to execute a recursive copy of a
1426 *      directory.
1427 *
1428 * Results:
1429 *      Standard Tcl result.
1430 *
1431 * Side effects:
1432 *      Depending on the value of type, src may be copied to dst.
1433 *
1434 *----------------------------------------------------------------------
1435 */
1436
1437static int
1438TraversalCopy(
1439    CONST TCHAR *nativeSrc,     /* Source pathname to copy. */
1440    CONST TCHAR *nativeDst,     /* Destination pathname of copy. */
1441    int type,                   /* Reason for call - see TraverseWinTree() */
1442    Tcl_DString *errorPtr)      /* If non-NULL, initialized DString filled
1443                                 * with UTF-8 name of file causing error. */
1444{
1445    switch (type) {
1446    case DOTREE_F:
1447        if (DoCopyFile(nativeSrc, nativeDst) == TCL_OK) {
1448            return TCL_OK;
1449        }
1450        break;
1451    case DOTREE_LINK:
1452        if (TclWinSymLinkCopyDirectory(nativeSrc, nativeDst) == TCL_OK) {
1453            return TCL_OK;
1454        }
1455        break;
1456    case DOTREE_PRED:
1457        if (DoCreateDirectory(nativeDst) == TCL_OK) {
1458            DWORD attr = (tclWinProcs->getFileAttributesProc)(nativeSrc);
1459
1460            if ((tclWinProcs->setFileAttributesProc)(nativeDst,
1461                    attr) != FALSE) {
1462                return TCL_OK;
1463            }
1464            TclWinConvertError(GetLastError());
1465        }
1466        break;
1467    case DOTREE_POSTD:
1468        return TCL_OK;
1469    }
1470
1471    /*
1472     * There shouldn't be a problem with src, because we already checked it to
1473     * get here.
1474     */
1475
1476    if (errorPtr != NULL) {
1477        Tcl_WinTCharToUtf(nativeDst, -1, errorPtr);
1478    }
1479    return TCL_ERROR;
1480}
1481
1482/*
1483 *----------------------------------------------------------------------
1484 *
1485 * TraversalDelete --
1486 *
1487 *      Called by function TraverseWinTree for every file and directory that
1488 *      it encounters in a directory hierarchy. This function unlinks files,
1489 *      and removes directories after all the containing files have been
1490 *      processed.
1491 *
1492 * Results:
1493 *      Standard Tcl result.
1494 *
1495 * Side effects:
1496 *      Files or directory specified by src will be deleted. If an error
1497 *      occurs, the windows error is converted to a Posix error and errno is
1498 *      set accordingly.
1499 *
1500 *----------------------------------------------------------------------
1501 */
1502
1503static int
1504TraversalDelete(
1505    CONST TCHAR *nativeSrc,     /* Source pathname to delete. */
1506    CONST TCHAR *dstPtr,        /* Not used. */
1507    int type,                   /* Reason for call - see TraverseWinTree() */
1508    Tcl_DString *errorPtr)      /* If non-NULL, initialized DString filled
1509                                 * with UTF-8 name of file causing error. */
1510{
1511    switch (type) {
1512    case DOTREE_F:
1513        if (TclpDeleteFile(nativeSrc) == TCL_OK) {
1514            return TCL_OK;
1515        }
1516        break;
1517    case DOTREE_LINK:
1518        if (DoRemoveJustDirectory(nativeSrc, 0, NULL) == TCL_OK) {
1519            return TCL_OK;
1520        }
1521        break;
1522    case DOTREE_PRED:
1523        return TCL_OK;
1524    case DOTREE_POSTD:
1525        if (DoRemoveJustDirectory(nativeSrc, 0, NULL) == TCL_OK) {
1526            return TCL_OK;
1527        }
1528        break;
1529    }
1530
1531    if (errorPtr != NULL) {
1532        Tcl_WinTCharToUtf(nativeSrc, -1, errorPtr);
1533    }
1534    return TCL_ERROR;
1535}
1536
1537/*
1538 *----------------------------------------------------------------------
1539 *
1540 * StatError --
1541 *
1542 *      Sets the object result with the appropriate error.
1543 *
1544 * Results:
1545 *      None.
1546 *
1547 * Side effects:
1548 *      The interp's object result is set with an error message based on the
1549 *      objIndex, fileName and errno.
1550 *
1551 *----------------------------------------------------------------------
1552 */
1553
1554static void
1555StatError(
1556    Tcl_Interp *interp,         /* The interp that has the error */
1557    Tcl_Obj *fileName)          /* The name of the file which caused the
1558                                 * error. */
1559{
1560    TclWinConvertError(GetLastError());
1561    Tcl_AppendResult(interp, "could not read \"", TclGetString(fileName),
1562            "\": ", Tcl_PosixError(interp), (char *) NULL);
1563}
1564
1565/*
1566 *----------------------------------------------------------------------
1567 *
1568 * GetWinFileAttributes --
1569 *
1570 *      Returns a Tcl_Obj containing the value of a file attribute. This
1571 *      routine gets the -hidden, -readonly or -system attribute.
1572 *
1573 * Results:
1574 *      Standard Tcl result and a Tcl_Obj in attributePtrPtr. The object will
1575 *      have ref count 0. If the return value is not TCL_OK, attributePtrPtr
1576 *      is not touched.
1577 *
1578 * Side effects:
1579 *      A new object is allocated if the file is valid.
1580 *
1581 *----------------------------------------------------------------------
1582 */
1583
1584static int
1585GetWinFileAttributes(
1586    Tcl_Interp *interp,         /* The interp we are using for errors. */
1587    int objIndex,               /* The index of the attribute. */
1588    Tcl_Obj *fileName,          /* The name of the file. */
1589    Tcl_Obj **attributePtrPtr)  /* A pointer to return the object with. */
1590{
1591    DWORD result;
1592    CONST TCHAR *nativeName;
1593    int attr;
1594
1595    nativeName = Tcl_FSGetNativePath(fileName);
1596    result = (*tclWinProcs->getFileAttributesProc)(nativeName);
1597
1598    if (result == 0xffffffff) {
1599        StatError(interp, fileName);
1600        return TCL_ERROR;
1601    }
1602
1603    attr = (int)(result & attributeArray[objIndex]);
1604    if ((objIndex == WIN_HIDDEN_ATTRIBUTE) && (attr != 0)) {
1605        /*
1606         * It is hidden. However there is a bug on some Windows OSes in which
1607         * root volumes (drives) formatted as NTFS are declared hidden when
1608         * they are not (and cannot be).
1609         *
1610         * We test for, and fix that case, here.
1611         */
1612
1613        int len;
1614        char *str = Tcl_GetStringFromObj(fileName,&len);
1615
1616        if (len < 4) {
1617            if (len == 0) {
1618                /*
1619                 * Not sure if this is possible, but we pass it on anyway.
1620                 */
1621            } else if (len == 1 && (str[0] == '/' || str[0] == '\\')) {
1622                /*
1623                 * Path is pointing to the root volume.
1624                 */
1625
1626                attr = 0;
1627            } else if ((str[1] == ':')
1628                    && (len == 2 || (str[2] == '/' || str[2] == '\\'))) {
1629                /*
1630                 * Path is of the form 'x:' or 'x:/' or 'x:\'
1631                 */
1632
1633                attr = 0;
1634            }
1635        }
1636    }
1637
1638    *attributePtrPtr = Tcl_NewBooleanObj(attr);
1639    return TCL_OK;
1640}
1641
1642/*
1643 *----------------------------------------------------------------------
1644 *
1645 * ConvertFileNameFormat --
1646 *
1647 *      Returns a Tcl_Obj containing either the long or short version of the
1648 *      file name.
1649 *
1650 * Results:
1651 *      Standard Tcl result and a Tcl_Obj in attributePtrPtr. The object will
1652 *      have ref count 0. If the return value is not TCL_OK, attributePtrPtr
1653 *      is not touched.
1654 *
1655 *      Warning: if you pass this function a drive name like 'c:' it will
1656 *      actually return the current working directory on that drive. To avoid
1657 *      this, make sure the drive name ends in a slash, like this 'c:/'.
1658 *
1659 * Side effects:
1660 *      A new object is allocated if the file is valid.
1661 *
1662 *----------------------------------------------------------------------
1663 */
1664
1665static int
1666ConvertFileNameFormat(
1667    Tcl_Interp *interp,         /* The interp we are using for errors. */
1668    int objIndex,               /* The index of the attribute. */
1669    Tcl_Obj *fileName,          /* The name of the file. */
1670    int longShort,              /* 0 to short name, 1 to long name. */
1671    Tcl_Obj **attributePtrPtr)  /* A pointer to return the object with. */
1672{
1673    int pathc, i;
1674    Tcl_Obj *splitPath;
1675
1676    splitPath = Tcl_FSSplitPath(fileName, &pathc);
1677
1678    if (splitPath == NULL || pathc == 0) {
1679        if (interp != NULL) {
1680            Tcl_AppendResult(interp, "could not read \"",
1681                    Tcl_GetString(fileName), "\": no such file or directory",
1682                    (char *) NULL);
1683        }
1684        goto cleanup;
1685    }
1686
1687    /*
1688     * We will decrement this again at the end.  It is safer to do this in
1689     * case any of the calls below retain a reference to splitPath.
1690     */
1691
1692    Tcl_IncrRefCount(splitPath);
1693
1694    for (i = 0; i < pathc; i++) {
1695        Tcl_Obj *elt;
1696        char *pathv;
1697        int pathLen;
1698
1699        Tcl_ListObjIndex(NULL, splitPath, i, &elt);
1700
1701        pathv = Tcl_GetStringFromObj(elt, &pathLen);
1702        if ((pathv[0] == '/') || ((pathLen == 3) && (pathv[1] == ':'))
1703                || (strcmp(pathv, ".") == 0) || (strcmp(pathv, "..") == 0)) {
1704            /*
1705             * Handle "/", "//machine/export", "c:/", "." or ".." by just
1706             * copying the string literally.  Uppercase the drive letter, just
1707             * because it looks better under Windows to do so.
1708             */
1709
1710        simple:
1711            /*
1712             * Here we are modifying the string representation in place.
1713             *
1714             * I believe this is legal, since this won't affect any file
1715             * representation this thing may have.
1716             */
1717
1718            pathv[0] = (char) Tcl_UniCharToUpper(UCHAR(pathv[0]));
1719        } else {
1720            Tcl_Obj *tempPath;
1721            Tcl_DString ds;
1722            Tcl_DString dsTemp;
1723            TCHAR *nativeName;
1724            char *tempString;
1725            int tempLen;
1726            WIN32_FIND_DATAT data;
1727            HANDLE handle;
1728            DWORD attr;
1729
1730            tempPath = Tcl_FSJoinPath(splitPath, i+1);
1731            Tcl_IncrRefCount(tempPath);
1732
1733            /*
1734             * We'd like to call Tcl_FSGetNativePath(tempPath) but that is
1735             * likely to lead to infinite loops.
1736             */
1737
1738            Tcl_DStringInit(&ds);
1739            tempString = Tcl_GetStringFromObj(tempPath,&tempLen);
1740            nativeName = Tcl_WinUtfToTChar(tempString, tempLen, &ds);
1741            Tcl_DecrRefCount(tempPath);
1742            handle = (*tclWinProcs->findFirstFileProc)(nativeName, &data);
1743            if (handle == INVALID_HANDLE_VALUE) {
1744                /*
1745                 * FindFirstFile() doesn't like root directories. We would
1746                 * only get a root directory here if the caller specified "c:"
1747                 * or "c:." and the current directory on the drive was the
1748                 * root directory
1749                 */
1750
1751                attr = (*tclWinProcs->getFileAttributesProc)(nativeName);
1752                if ((attr!=0xFFFFFFFF) && (attr & FILE_ATTRIBUTE_DIRECTORY)) {
1753                    Tcl_DStringFree(&ds);
1754                    goto simple;
1755                }
1756            }
1757
1758            if (handle == INVALID_HANDLE_VALUE) {
1759                Tcl_DStringFree(&ds);
1760                if (interp != NULL) {
1761                    StatError(interp, fileName);
1762                }
1763                goto cleanup;
1764            }
1765            if (tclWinProcs->useWide) {
1766                nativeName = (TCHAR *) data.w.cAlternateFileName;
1767                if (longShort) {
1768                    if (data.w.cFileName[0] != '\0') {
1769                        nativeName = (TCHAR *) data.w.cFileName;
1770                    }
1771                } else {
1772                    if (data.w.cAlternateFileName[0] == '\0') {
1773                        nativeName = (TCHAR *) data.w.cFileName;
1774                    }
1775                }
1776            } else {
1777                nativeName = (TCHAR *) data.a.cAlternateFileName;
1778                if (longShort) {
1779                    if (data.a.cFileName[0] != '\0') {
1780                        nativeName = (TCHAR *) data.a.cFileName;
1781                    }
1782                } else {
1783                    if (data.a.cAlternateFileName[0] == '\0') {
1784                        nativeName = (TCHAR *) data.a.cFileName;
1785                    }
1786                }
1787            }
1788
1789            /*
1790             * Purify reports a extraneous UMR in Tcl_WinTCharToUtf() trying
1791             * to dereference nativeName as a Unicode string. I have proven to
1792             * myself that purify is wrong by running the following example
1793             * when nativeName == data.w.cAlternateFileName and noting that
1794             * purify doesn't complain about the first line, but does complain
1795             * about the second.
1796             *
1797             *  fprintf(stderr, "%d\n", data.w.cAlternateFileName[0]);
1798             *  fprintf(stderr, "%d\n", ((WCHAR *) nativeName)[0]);
1799             */
1800
1801            Tcl_DStringInit(&dsTemp);
1802            Tcl_WinTCharToUtf(nativeName, -1, &dsTemp);
1803
1804            /*
1805             * Deal with issues of tildes being absolute.
1806             */
1807
1808            if (Tcl_DStringValue(&dsTemp)[0] == '~') {
1809                tempPath = Tcl_NewStringObj("./",2);
1810                Tcl_AppendToObj(tempPath, Tcl_DStringValue(&dsTemp),
1811                        Tcl_DStringLength(&dsTemp));
1812            } else {
1813                tempPath = Tcl_NewStringObj(Tcl_DStringValue(&dsTemp),
1814                        Tcl_DStringLength(&dsTemp));
1815            }
1816            Tcl_ListObjReplace(NULL, splitPath, i, 1, 1, &tempPath);
1817            Tcl_DStringFree(&ds);
1818            Tcl_DStringFree(&dsTemp);
1819            FindClose(handle);
1820        }
1821    }
1822
1823    *attributePtrPtr = Tcl_FSJoinPath(splitPath, -1);
1824
1825    if (splitPath != NULL) {
1826        /*
1827         * Unfortunately, the object we will return may have its only refCount
1828         * as part of the list splitPath. This means if we free splitPath, the
1829         * object will disappear. So, we have to be very careful here.
1830         * Unfortunately this means we must manipulate the object's refCount
1831         * directly.
1832         */
1833
1834        Tcl_IncrRefCount(*attributePtrPtr);
1835        Tcl_DecrRefCount(splitPath);
1836        --(*attributePtrPtr)->refCount;
1837    }
1838    return TCL_OK;
1839
1840  cleanup:
1841    if (splitPath != NULL) {
1842        Tcl_DecrRefCount(splitPath);
1843    }
1844
1845    return TCL_ERROR;
1846}
1847
1848/*
1849 *----------------------------------------------------------------------
1850 *
1851 * GetWinFileLongName --
1852 *
1853 *      Returns a Tcl_Obj containing the long version of the file name.
1854 *
1855 * Results:
1856 *      Standard Tcl result and a Tcl_Obj in attributePtrPtr. The object will
1857 *      have ref count 0. If the return value is not TCL_OK, attributePtrPtr
1858 *      is not touched.
1859 *
1860 * Side effects:
1861 *      A new object is allocated if the file is valid.
1862 *
1863 *----------------------------------------------------------------------
1864 */
1865
1866static int
1867GetWinFileLongName(
1868    Tcl_Interp *interp,         /* The interp we are using for errors. */
1869    int objIndex,               /* The index of the attribute. */
1870    Tcl_Obj *fileName,          /* The name of the file. */
1871    Tcl_Obj **attributePtrPtr)  /* A pointer to return the object with. */
1872{
1873    return ConvertFileNameFormat(interp, objIndex, fileName, 1,
1874            attributePtrPtr);
1875}
1876
1877/*
1878 *----------------------------------------------------------------------
1879 *
1880 * GetWinFileShortName --
1881 *
1882 *      Returns a Tcl_Obj containing the short version of the file name.
1883 *
1884 * Results:
1885 *      Standard Tcl result and a Tcl_Obj in attributePtrPtr. The object will
1886 *      have ref count 0. If the return value is not TCL_OK, attributePtrPtr
1887 *      is not touched.
1888 *
1889 * Side effects:
1890 *      A new object is allocated if the file is valid.
1891 *
1892 *----------------------------------------------------------------------
1893 */
1894
1895static int
1896GetWinFileShortName(
1897    Tcl_Interp *interp,         /* The interp we are using for errors. */
1898    int objIndex,               /* The index of the attribute. */
1899    Tcl_Obj *fileName,          /* The name of the file. */
1900    Tcl_Obj **attributePtrPtr)  /* A pointer to return the object with. */
1901{
1902    return ConvertFileNameFormat(interp, objIndex, fileName, 0,
1903            attributePtrPtr);
1904}
1905
1906/*
1907 *----------------------------------------------------------------------
1908 *
1909 * SetWinFileAttributes --
1910 *
1911 *      Set the file attributes to the value given by attributePtr. This
1912 *      routine sets the -hidden, -readonly, or -system attributes.
1913 *
1914 * Results:
1915 *      Standard TCL error.
1916 *
1917 * Side effects:
1918 *      The file's attribute is set.
1919 *
1920 *----------------------------------------------------------------------
1921 */
1922
1923static int
1924SetWinFileAttributes(
1925    Tcl_Interp *interp,         /* The interp we are using for errors. */
1926    int objIndex,               /* The index of the attribute. */
1927    Tcl_Obj *fileName,          /* The name of the file. */
1928    Tcl_Obj *attributePtr)      /* The new value of the attribute. */
1929{
1930    DWORD fileAttributes;
1931    int yesNo;
1932    int result;
1933    CONST TCHAR *nativeName;
1934
1935    nativeName = Tcl_FSGetNativePath(fileName);
1936    fileAttributes = (*tclWinProcs->getFileAttributesProc)(nativeName);
1937
1938    if (fileAttributes == 0xffffffff) {
1939        StatError(interp, fileName);
1940        return TCL_ERROR;
1941    }
1942
1943    result = Tcl_GetBooleanFromObj(interp, attributePtr, &yesNo);
1944    if (result != TCL_OK) {
1945        return result;
1946    }
1947
1948    if (yesNo) {
1949        fileAttributes |= (attributeArray[objIndex]);
1950    } else {
1951        fileAttributes &= ~(attributeArray[objIndex]);
1952    }
1953
1954    if (!(*tclWinProcs->setFileAttributesProc)(nativeName, fileAttributes)) {
1955        StatError(interp, fileName);
1956        return TCL_ERROR;
1957    }
1958
1959    return result;
1960}
1961
1962/*
1963 *----------------------------------------------------------------------
1964 *
1965 * SetWinFileLongName --
1966 *
1967 *      The attribute in question is a readonly attribute and cannot be set.
1968 *
1969 * Results:
1970 *      TCL_ERROR
1971 *
1972 * Side effects:
1973 *      The object result is set to a pertinent error message.
1974 *
1975 *----------------------------------------------------------------------
1976 */
1977
1978static int
1979CannotSetAttribute(
1980    Tcl_Interp *interp,         /* The interp we are using for errors. */
1981    int objIndex,               /* The index of the attribute. */
1982    Tcl_Obj *fileName,          /* The name of the file. */
1983    Tcl_Obj *attributePtr)      /* The new value of the attribute. */
1984{
1985    Tcl_AppendResult(interp, "cannot set attribute \"",
1986            tclpFileAttrStrings[objIndex], "\" for file \"",
1987            Tcl_GetString(fileName), "\": attribute is readonly",
1988            (char *) NULL);
1989    return TCL_ERROR;
1990}
1991
1992
1993/*
1994 *---------------------------------------------------------------------------
1995 *
1996 * TclpObjListVolumes --
1997 *
1998 *      Lists the currently mounted volumes
1999 *
2000 * Results:
2001 *      The list of volumes.
2002 *
2003 * Side effects:
2004 *      None
2005 *
2006 *---------------------------------------------------------------------------
2007 */
2008
2009Tcl_Obj*
2010TclpObjListVolumes(void)
2011{
2012    Tcl_Obj *resultPtr, *elemPtr;
2013    char buf[40 * 4];           /* There couldn't be more than 30 drives??? */
2014    int i;
2015    char *p;
2016
2017    resultPtr = Tcl_NewObj();
2018
2019    /*
2020     * On Win32s:
2021     * GetLogicalDriveStrings() isn't implemented.
2022     * GetLogicalDrives() returns incorrect information.
2023     */
2024
2025    if (GetLogicalDriveStringsA(sizeof(buf), buf) == 0) {
2026        /*
2027         * GetVolumeInformation() will detects all drives, but causes
2028         * chattering on empty floppy drives. We only do this if
2029         * GetLogicalDriveStrings() didn't work. It has also been reported
2030         * that on some laptops it takes a while for GetVolumeInformation() to
2031         * return when pinging an empty floppy drive, another reason to try to
2032         * avoid calling it.
2033         */
2034
2035        buf[1] = ':';
2036        buf[2] = '/';
2037        buf[3] = '\0';
2038
2039        for (i = 0; i < 26; i++) {
2040            buf[0] = (char) ('a' + i);
2041            if (GetVolumeInformationA(buf, NULL, 0, NULL, NULL, NULL, NULL, 0)
2042                    || (GetLastError() == ERROR_NOT_READY)) {
2043                elemPtr = Tcl_NewStringObj(buf, -1);
2044                Tcl_ListObjAppendElement(NULL, resultPtr, elemPtr);
2045            }
2046        }
2047    } else {
2048        for (p = buf; *p != '\0'; p += 4) {
2049            p[2] = '/';
2050            elemPtr = Tcl_NewStringObj(p, -1);
2051            Tcl_ListObjAppendElement(NULL, resultPtr, elemPtr);
2052        }
2053    }
2054
2055    Tcl_IncrRefCount(resultPtr);
2056    return resultPtr;
2057}
2058
2059/*
2060 * Local Variables:
2061 * mode: c
2062 * c-basic-offset: 4
2063 * fill-column: 78
2064 * End:
2065 */
Note: See TracBrowser for help on using the repository browser.