Back to index

enigmail  1.4.3
nsinstall_win.c
Go to the documentation of this file.
00001 /*
00002  * The nsinstall command for Win32
00003  *
00004  * Our gmake makefiles use the nsinstall command to create the
00005  * object directories or installing headers and libs. This code was originally
00006  * taken from shmsdos.c
00007  */
00008 
00009 #include <direct.h>
00010 #include <stdio.h>
00011 #include <string.h>
00012 #include <assert.h>
00013 #include <windows.h>
00014 #pragma hdrstop
00015 
00016 /*
00017  * sh_FileFcn --
00018  *
00019  * A function that operates on a file.  The pathname is either
00020  * absolute or relative to the current directory, and contains
00021  * no wildcard characters such as * and ?.   Additional arguments
00022  * can be passed to the function via the arg pointer.
00023  */
00024 
00025 typedef BOOL (*sh_FileFcn)(
00026         wchar_t *pathName,
00027         WIN32_FIND_DATA *fileData,
00028         void *arg);
00029 
00030 static int shellCp (wchar_t **pArgv); 
00031 static int shellNsinstall (wchar_t **pArgv);
00032 static int shellMkdir (wchar_t **pArgv); 
00033 static BOOL sh_EnumerateFiles(const wchar_t *pattern, const wchar_t *where,
00034         sh_FileFcn fileFcn, void *arg, int *nFiles);
00035 static const char *sh_GetLastErrorMessage(void);
00036 static BOOL sh_DoCopy(wchar_t *srcFileName, DWORD srcFileAttributes,
00037         wchar_t *dstFileName, DWORD dstFileAttributes,
00038         int force, int recursive);
00039 
00040 #define LONGPATH_PREFIX L"\\\\?\\"
00041 #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
00042 #define STR_LEN(a) (ARRAY_LEN(a) - 1)
00043 
00044 #ifdef __MINGW32__
00045 
00046 /* MingW currently does not implement a wide version of the
00047    startup routines.  Workaround is to implement something like
00048    it ourselves. */
00049 
00050 #include <shellapi.h>
00051 
00052 int wmain(int argc, WCHAR **argv);
00053 
00054 int main(int argc, char **argv)
00055 {
00056     int result;
00057     wchar_t *commandLine = GetCommandLineW();
00058     int argcw = 0;
00059     wchar_t **_argvw = CommandLineToArgvW( commandLine, &argcw );
00060     wchar_t *argvw[argcw + 1];
00061     int i;
00062     if (!_argvw)
00063         return 127;
00064     /* CommandLineToArgvW doesn't output the ending NULL so
00065        we have to manually add it on */
00066     for ( i = 0; i < argcw; i++ )
00067         argvw[i] = _argvw[i];
00068     argvw[argcw] = NULL;
00069 
00070     result = wmain(argcw, argvw);
00071     LocalFree(_argvw);
00072     return result;
00073 }
00074 #endif /* __MINGW32__ */
00075 
00076 /* changes all forward slashes in token to backslashes */
00077 void changeForwardSlashesToBackSlashes ( wchar_t *arg )
00078 {
00079     if ( arg == NULL )
00080         return;
00081 
00082     while ( *arg ) {
00083         if ( *arg == '/' )
00084             *arg = '\\';
00085         arg++;
00086     }
00087 }
00088 
00089 int wmain(int argc, wchar_t *argv[ ])
00090 {
00091     return shellNsinstall ( argv + 1 );
00092 }
00093 
00094 static int
00095 shellNsinstall (wchar_t **pArgv)
00096 {
00097     int retVal = 0;     /* exit status */
00098     int dirOnly = 0;    /* 1 if and only if -D is specified */
00099     wchar_t **pSrc;
00100     wchar_t **pDst;
00101 
00102     /*
00103      * Process the command-line options.  We ignore the
00104      * options except for -D.  Some options, such as -m,
00105      * are followed by an argument.  We need to skip the
00106      * argument too.
00107      */
00108     while ( *pArgv && **pArgv == '-' ) {
00109         wchar_t c = (*pArgv)[1];  /* The char after '-' */
00110 
00111         if ( c == 'D' ) {
00112             dirOnly = 1;
00113         } else if ( c == 'm' ) {
00114             pArgv++;  /* skip the next argument */
00115         }
00116         pArgv++;
00117     }
00118 
00119     if ( !dirOnly ) {
00120         /* There are files to install.  Get source files */
00121         if ( *pArgv ) {
00122             pSrc = pArgv++;
00123         } else {
00124             fprintf( stderr, "nsinstall: not enough arguments\n");
00125             return 3;
00126         }
00127     }
00128 
00129     /* Get to last token to find destination directory */
00130     if ( *pArgv ) {
00131         pDst = pArgv++;
00132         if ( dirOnly && *pArgv ) {
00133             fprintf( stderr, "nsinstall: too many arguments with -D\n");
00134             return 3;
00135         }
00136     } else {
00137         fprintf( stderr, "nsinstall: not enough arguments\n");
00138         return 3;
00139     }
00140     while ( *pArgv ) 
00141         pDst = pArgv++;
00142 
00143     retVal = shellMkdir ( pDst );
00144     if ( retVal )
00145         return retVal;
00146     if ( !dirOnly )
00147         retVal = shellCp ( pSrc );
00148     return retVal;
00149 }
00150 
00151 static int
00152 shellMkdir (wchar_t **pArgv) 
00153 {
00154     int retVal = 0; /* assume valid return */
00155     wchar_t *arg;
00156     wchar_t *pArg;
00157     wchar_t path[_MAX_PATH];
00158     wchar_t tmpPath[_MAX_PATH];
00159     wchar_t *pTmpPath = tmpPath;
00160 
00161     /* All the options are simply ignored in this implementation */
00162     while ( *pArgv && **pArgv == '-' ) {
00163         if ( (*pArgv)[1] == 'm' ) {
00164             pArgv++;  /* skip the next argument (mode) */
00165         }
00166         pArgv++;
00167     }
00168 
00169     while ( *pArgv ) {
00170         arg = *pArgv;
00171         changeForwardSlashesToBackSlashes ( arg );
00172         pArg = arg;
00173         pTmpPath = tmpPath;
00174         while ( 1 ) {
00175             /* create part of path */
00176             while ( *pArg ) {
00177                 *pTmpPath++ = *pArg++;
00178                 if ( *pArg == '\\' )
00179                     break;
00180             }
00181             *pTmpPath = '\0';
00182 
00183             /* check if directory already exists */
00184             _wgetcwd ( path, _MAX_PATH );
00185             if ( _wchdir ( tmpPath ) == -1 &&
00186                  _wmkdir ( tmpPath ) == -1 && // might have hit EEXIST
00187                  _wchdir ( tmpPath ) == -1) { // so try again
00188                 char buf[2048];
00189                 _snprintf(buf, 2048, "Could not create the directory: %S",
00190                           tmpPath);
00191                 perror ( buf );
00192                 retVal = 3;
00193                 break;
00194             } else {
00195                 // get back to the cwd
00196                 _wchdir ( path );
00197             }
00198             if ( *pArg == '\0' )      /* complete path? */
00199                 break;
00200             /* loop for next directory */
00201         }
00202 
00203         pArgv++;
00204     }
00205     return retVal;
00206 }
00207 
00208 static const char *
00209 sh_GetLastErrorMessage()
00210 {
00211     static char buf[128];
00212 
00213     FormatMessageA(
00214             FORMAT_MESSAGE_FROM_SYSTEM,
00215             NULL,
00216             GetLastError(),
00217             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),  /* default language */
00218             buf,
00219             sizeof(buf),
00220             NULL
00221     );
00222     return buf;
00223 }
00224 
00225 /*
00226  * struct sh_FileData --
00227  *
00228  * A pointer to the sh_FileData structure is passed into sh_RecordFileData,
00229  * which will fill in the fields.
00230  */
00231 
00232 struct sh_FileData {
00233     wchar_t pathName[_MAX_PATH];
00234     DWORD dwFileAttributes;
00235 };
00236 
00237 /*
00238  * sh_RecordFileData --
00239  *
00240  * Record the pathname and attributes of the file in
00241  * the sh_FileData structure pointed to by arg.
00242  *
00243  * Always return TRUE (successful completion).
00244  *
00245  * This function is intended to be passed into sh_EnumerateFiles
00246  * to see if a certain pattern expands to exactly one file/directory,
00247  * and if so, record its pathname and attributes.
00248  */
00249 
00250 static BOOL
00251 sh_RecordFileData(wchar_t *pathName, WIN32_FIND_DATA *findData, void *arg)
00252 {
00253     struct sh_FileData *fData = (struct sh_FileData *) arg;
00254 
00255     wcscpy(fData->pathName, pathName);
00256     fData->dwFileAttributes = findData->dwFileAttributes;
00257     return TRUE;
00258 }
00259 
00260 static BOOL
00261 sh_DoCopy(wchar_t *srcFileName,
00262           DWORD srcFileAttributes,
00263           wchar_t *dstFileName,
00264           DWORD dstFileAttributes,
00265           int force,
00266           int recursive
00267 )
00268 {
00269     if (dstFileAttributes != 0xFFFFFFFF) {
00270         if ((dstFileAttributes & FILE_ATTRIBUTE_READONLY) && force) {
00271             dstFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
00272             SetFileAttributes(dstFileName, dstFileAttributes);
00273         }
00274     }
00275 
00276     if (srcFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
00277         fprintf(stderr, "nsinstall: %ls is a directory\n",
00278                 srcFileName);
00279         return FALSE;
00280     } else {
00281         DWORD r;
00282         wchar_t longSrc[1004] = LONGPATH_PREFIX;
00283         wchar_t longDst[1004] = LONGPATH_PREFIX;
00284         r = GetFullPathName(srcFileName, 1000, longSrc + STR_LEN(LONGPATH_PREFIX), NULL);
00285         if (!r) {
00286             fprintf(stderr, "nsinstall: couldn't get full path of %ls: %s\n",
00287                     srcFileName, sh_GetLastErrorMessage());
00288             return FALSE;
00289         }
00290         r = GetFullPathName(dstFileName, 1000, longDst + ARRAY_LEN(LONGPATH_PREFIX) - 1, NULL);
00291         if (!r) {
00292             fprintf(stderr, "nsinstall: couldn't get full path of %ls: %s\n",
00293                     dstFileName, sh_GetLastErrorMessage());
00294             return FALSE;
00295         }
00296 
00297         if (!CopyFile(longSrc, longDst, FALSE)) {
00298             fprintf(stderr, "nsinstall: cannot copy %ls to %ls: %s\n",
00299                     srcFileName, dstFileName, sh_GetLastErrorMessage());
00300             return FALSE;
00301         }
00302     }
00303     return TRUE;
00304 }
00305 
00306 /*
00307  * struct sh_CpCmdArg --
00308  *
00309  * A pointer to the sh_CpCmdArg structure is passed into sh_CpFileCmd.
00310  * The sh_CpCmdArg contains information about the cp command, and
00311  * provide a buffer for constructing the destination file name.
00312  */
00313 
00314 struct sh_CpCmdArg {
00315     int force;                /* -f option, ok to overwrite an existing
00316                                * read-only destination file */
00317     int recursive;            /* -r or -R option, recursively copy
00318                                * directories. Note: this field is not used
00319                                * by nsinstall and should always be 0. */
00320     wchar_t *dstFileName;        /* a buffer for constructing the destination
00321                                * file name */
00322     wchar_t *dstFileNameMarker;  /* points to where in the dstFileName buffer
00323                                * we should write the file component of the
00324                                * destination file */
00325 };
00326 
00327 /*
00328  * sh_CpFileCmd --
00329  *
00330  * Copy a file to the destination directory
00331  * 
00332  * This function is intended to be passed into sh_EnumerateFiles to
00333  * copy all the files specified by the pattern to the destination
00334  * directory.
00335  *
00336  * Return TRUE if the file is successfully copied, and FALSE otherwise.
00337  */
00338 
00339 static BOOL
00340 sh_CpFileCmd(wchar_t *pathName, WIN32_FIND_DATA *findData, void *cpArg)
00341 {
00342     BOOL retVal = TRUE;
00343     struct sh_CpCmdArg *arg = (struct sh_CpCmdArg *) cpArg;
00344 
00345     wcscpy(arg->dstFileNameMarker, findData->cFileName);
00346     return sh_DoCopy(pathName, findData->dwFileAttributes,
00347             arg->dstFileName, GetFileAttributes(arg->dstFileName),
00348             arg->force, arg->recursive);
00349 }
00350 
00351 static int
00352 shellCp (wchar_t **pArgv) 
00353 {
00354     int retVal = 0;
00355     wchar_t **pSrc;
00356     wchar_t **pDst;
00357     struct sh_CpCmdArg arg;
00358     struct sh_FileData dstData;
00359     int dstIsDir = 0;
00360     int n;
00361 
00362     arg.force = 0;
00363     arg.recursive = 0;
00364     arg.dstFileName = dstData.pathName;
00365     arg.dstFileNameMarker = 0;
00366 
00367     while (*pArgv && **pArgv == '-') {
00368         wchar_t *p = *pArgv;
00369 
00370         while (*(++p)) {
00371             if (*p == 'f') {
00372                 arg.force = 1;
00373             }
00374         }
00375         pArgv++;
00376     }
00377 
00378     /* the first source file */
00379     if (*pArgv) {
00380         pSrc = pArgv++;
00381     } else {
00382         fprintf(stderr, "nsinstall: not enough arguments\n");
00383         return 3;
00384     }
00385 
00386     /* get to the last token to find destination */
00387     if (*pArgv) {
00388         pDst = pArgv++;
00389     } else {
00390         fprintf(stderr, "nsinstall: not enough arguments\n");
00391         return 3;
00392     }
00393     while (*pArgv) {
00394         pDst = pArgv++;
00395     }
00396 
00397     /*
00398      * The destination pattern must unambiguously expand to exactly
00399      * one file or directory.
00400      */
00401 
00402     changeForwardSlashesToBackSlashes(*pDst);
00403     sh_EnumerateFiles(*pDst, *pDst, sh_RecordFileData, &dstData, &n);
00404     assert(n >= 0);
00405     if (n == 1) {
00406         /*
00407          * Is the destination a file or directory?
00408          */
00409 
00410         if (dstData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
00411             dstIsDir = 1;
00412         }
00413     } else if (n > 1) {
00414         fprintf(stderr, "nsinstall: %ls: ambiguous destination file "
00415                 "or directory\n", *pDst);
00416         return 3;
00417     } else {
00418         /*
00419          * n == 0, meaning that destination file or directory does
00420          * not exist.  In this case the destination file directory
00421          * name must be fully specified.
00422          */
00423 
00424         wchar_t *p;
00425 
00426         for (p = *pDst; *p; p++) {
00427             if (*p == '*' || *p == '?') {
00428                 fprintf(stderr, "nsinstall: %ls: No such file or directory\n",
00429                         *pDst);
00430                 return 3;
00431             }
00432         }
00433 
00434         /*
00435          * Do not include the trailing \, if any, unless it is a root
00436          * directory (\ or X:\).
00437          */
00438 
00439         if (p > *pDst && p[-1] == '\\' && p != *pDst + 1 && p[-2] != ':') {
00440             p[-1] = '\0';
00441         }
00442         wcscpy(dstData.pathName, *pDst);
00443         dstData.dwFileAttributes = 0xFFFFFFFF;
00444     }
00445 
00446     /*
00447      * If there are two or more source files, the destination has
00448      * to be a directory.
00449      */
00450 
00451     if (pDst - pSrc > 1 && !dstIsDir) {
00452         fprintf(stderr, "nsinstall: cannot copy more than"
00453                 " one file to the same destination file\n");
00454         return 3;
00455     }
00456 
00457     if (dstIsDir) {
00458         arg.dstFileNameMarker = arg.dstFileName + wcslen(arg.dstFileName);
00459 
00460         /*
00461          * Now arg.dstFileNameMarker is pointing to the null byte at the
00462          * end of string.  We want to make sure that there is a \ at the
00463          * end of string, and arg.dstFileNameMarker should point right
00464          * after that \. 
00465          */
00466 
00467         if (arg.dstFileNameMarker[-1] != '\\') {
00468             *(arg.dstFileNameMarker++) = '\\';
00469         }
00470     }
00471     
00472     if (!dstIsDir) {
00473         struct sh_FileData srcData;
00474 
00475         assert(pDst - pSrc == 1);
00476         changeForwardSlashesToBackSlashes(*pSrc);
00477         sh_EnumerateFiles(*pSrc, *pSrc, sh_RecordFileData, &srcData, &n);
00478         if (n == 0) {
00479             fprintf(stderr, "nsinstall: %ls: No such file or directory\n",
00480                     *pSrc);
00481             retVal = 3;
00482         } else if (n > 1) {
00483             fprintf(stderr, "nsinstall: cannot copy more than one file or "
00484                     "directory to the same destination\n");
00485             retVal = 3;
00486         } else {
00487             assert(n == 1);
00488             if (sh_DoCopy(srcData.pathName, srcData.dwFileAttributes,
00489                     dstData.pathName, dstData.dwFileAttributes,
00490                     arg.force, arg.recursive) == FALSE) {
00491                 retVal = 3;
00492             }
00493         }
00494         return retVal;
00495     }
00496 
00497     for ( ; *pSrc != *pDst; pSrc++) {
00498         BOOL rv;
00499 
00500         changeForwardSlashesToBackSlashes(*pSrc);
00501         rv = sh_EnumerateFiles(*pSrc, *pSrc, sh_CpFileCmd, &arg, &n);
00502         if (rv == FALSE) {
00503             retVal = 3;
00504         } else {
00505             if (n == 0) {
00506                 fprintf(stderr, "nsinstall: %ls: No such file or directory\n",
00507                         *pSrc);
00508                 retVal = 3;
00509             }
00510         }
00511     }
00512 
00513     return retVal;
00514 }
00515 
00516 /*
00517  * sh_EnumerateFiles --
00518  *
00519  * Enumerate all the files in the specified pattern, which is a pathname
00520  * containing possibly wildcard characters such as * and ?.  fileFcn
00521  * is called on each file, passing the expanded file name, a pointer
00522  * to the file's WIN32_FILE_DATA, and the arg pointer.
00523  * 
00524  * It is assumed that there are no wildcard characters before the
00525  * character pointed to by 'where'.
00526  *
00527  * On return, *nFiles stores the number of files enumerated.  *nFiles is
00528  * set to this number whether sh_EnumerateFiles or 'fileFcn' succeeds
00529  * or not.
00530  *
00531  * Return TRUE if the files are successfully enumerated and all
00532  * 'fileFcn' invocations succeeded.  Return FALSE if something went
00533  * wrong.
00534  */
00535 
00536 static BOOL sh_EnumerateFiles(
00537         const wchar_t *pattern,
00538         const wchar_t *where,
00539         sh_FileFcn fileFcn,
00540         void *arg,
00541         int *nFiles
00542         )
00543 {
00544     WIN32_FIND_DATA fileData;
00545     HANDLE hSearch;
00546     const wchar_t *src;
00547     wchar_t *dst;
00548     wchar_t fileName[_MAX_PATH];
00549     wchar_t *fileNameMarker = fileName;
00550     wchar_t *oldFileNameMarker;
00551     BOOL hasWildcard = FALSE;
00552     BOOL retVal = TRUE;
00553     BOOL patternEndsInDotStar = FALSE;
00554     BOOL patternEndsInDot = FALSE;  /* a special case of
00555                                      * patternEndsInDotStar */
00556     int numDotsInPattern;
00557     int len;
00558     
00559     /*
00560      * Windows expands patterns ending in ".", ".*", ".**", etc.
00561      * differently from the glob expansion on Unix.  For example,
00562      * both "foo." and "foo.*" match "foo", and "*.*" matches
00563      * everything, including filenames with no dots.  So we need
00564      * to throw away extra files returned by the FindNextFile()
00565      * function.  We require that a matched filename have at least
00566      * the number of dots in the pattern.
00567      */
00568     len = wcslen(pattern);
00569     if (len >= 2) {
00570         /* Start from the end of pattern and go backward */
00571         const wchar_t *p = &pattern[len - 1];
00572 
00573         /* We can have zero or more *'s */
00574         while (p >= pattern && *p == '*') {
00575             p--;
00576         }
00577         if (p >= pattern && *p == '.') {
00578             patternEndsInDotStar = TRUE;
00579             if (p == &pattern[len - 1]) {
00580                 patternEndsInDot = TRUE;
00581             }
00582             p--;
00583             numDotsInPattern = 1;
00584             while (p >= pattern && *p != '\\') {
00585                 if (*p == '.') {
00586                     numDotsInPattern++;
00587                 }
00588                 p--;
00589             }
00590         }
00591     }
00592 
00593     *nFiles = 0;
00594 
00595     /*
00596      * Copy pattern to fileName, but only up to and not including
00597      * the first \ after the first wildcard letter.
00598      *
00599      * Make fileNameMarker point to one of the following:
00600      * - the start of fileName, if fileName does not contain any \.
00601      * - right after the \ before the first wildcard letter, if there is
00602      *   a wildcard character.
00603      * - right after the last \, if there is no wildcard character.
00604      */
00605 
00606     dst = fileName;
00607     src = pattern;
00608     while (src < where) {
00609         if (*src == '\\') {
00610             oldFileNameMarker = fileNameMarker;
00611             fileNameMarker = dst + 1;
00612         }
00613         *(dst++) = *(src++);
00614     }
00615 
00616     while (*src && *src != '*' && *src != '?') {
00617         if (*src == '\\') {
00618             oldFileNameMarker = fileNameMarker;
00619             fileNameMarker = dst + 1;
00620         }
00621         *(dst++) = *(src++);
00622     }
00623 
00624     if (*src) {
00625         /*
00626          * Must have seen the first wildcard letter
00627          */
00628 
00629         hasWildcard = TRUE;
00630         while (*src && *src != '\\') {
00631             *(dst++) = *(src++);
00632         }
00633     }
00634     
00635     /* Now src points to either null or \ */
00636 
00637     assert(*src == '\0' || *src == '\\');
00638     assert(hasWildcard || *src == '\0');
00639     *dst = '\0';
00640 
00641     /*
00642      * If the pattern does not contain any wildcard characters, then
00643      * we don't need to go the FindFirstFile route.
00644      */
00645 
00646     if (!hasWildcard) {
00647         /*
00648          * See if it is the root directory, \, or X:\.
00649          */
00650 
00651         assert(!wcscmp(fileName, pattern));
00652         assert(wcslen(fileName) >= 1);
00653         if (dst[-1] == '\\' && (dst == fileName + 1 || dst[-2] == ':')) {
00654             fileData.cFileName[0] = '\0';
00655         } else {
00656             /*
00657              * Do not include the trailing \, if any
00658              */
00659 
00660             if (dst[-1] == '\\') {
00661                 assert(*fileNameMarker == '\0');
00662                 dst[-1] = '\0';
00663                 fileNameMarker = oldFileNameMarker;
00664             } 
00665             wcscpy(fileData.cFileName, fileNameMarker);
00666         }
00667         fileData.dwFileAttributes = GetFileAttributes(fileName);
00668         if (fileData.dwFileAttributes == 0xFFFFFFFF) {
00669             return TRUE;
00670         }
00671         *nFiles = 1;
00672         return (*fileFcn)(fileName, &fileData, arg);
00673     }
00674 
00675     hSearch = FindFirstFile(fileName, &fileData);
00676     if (hSearch == INVALID_HANDLE_VALUE) {
00677         return retVal;
00678     }
00679 
00680     do {
00681         if (!wcscmp(fileData.cFileName, L".")
00682                 || !wcscmp(fileData.cFileName, L"..")) {
00683             /* 
00684              * Skip over . and ..
00685              */
00686 
00687             continue;
00688         }
00689 
00690         if (patternEndsInDotStar) {
00691             int nDots = 0;
00692             wchar_t *p = fileData.cFileName;
00693             while (*p) {
00694                 if (*p == '.') {
00695                     nDots++;
00696                 }
00697                 p++;
00698             }
00699             /* Now p points to the null byte at the end of file name */
00700             if (patternEndsInDot && (p == fileData.cFileName
00701                     || p[-1] != '.')) {
00702                 /*
00703                  * File name does not end in dot.  Skip this file.
00704                  * Note: windows file name probably cannot end in dot,
00705                  * but we do this check anyway.
00706                  */
00707                 continue;
00708             }
00709             if (nDots < numDotsInPattern) {
00710                 /*
00711                  * Not enough dots in file name.  Must be an extra
00712                  * file in matching .* pattern.  Skip this file.
00713                  */
00714                 continue;
00715             }
00716         }
00717 
00718         wcscpy(fileNameMarker, fileData.cFileName);
00719         if (*src && *(src + 1)) {
00720             /*
00721              * More to go.  Recurse.
00722              */
00723 
00724             int n;
00725 
00726             assert(*src == '\\');
00727             where = fileName + wcslen(fileName);
00728             wcscat(fileName, src);
00729             sh_EnumerateFiles(fileName, where, fileFcn, arg, &n);
00730             *nFiles += n;
00731         } else {
00732             assert(wcschr(fileName, '*') == NULL);
00733             assert(wcschr(fileName, '?') == NULL);
00734             (*nFiles)++;
00735             if ((*fileFcn)(fileName, &fileData, arg) == FALSE) {
00736                 retVal = FALSE;
00737             }
00738         }
00739     } while (FindNextFile(hSearch, &fileData));
00740 
00741     FindClose(hSearch);
00742     return retVal;
00743 }