Fix GCC uninit-var warning (no bug, r=brendan).
[mozilla-central.git] / config / nsinstall_win.c
blob4a0656364d37f7b9fc888fa71e5e40795c5e9623
1 /*
2 * The nsinstall command for Win32
4 * Our gmake makefiles use the nsinstall command to create the
5 * object directories or installing headers and libs. This code was originally
6 * taken from shmsdos.c
7 */
9 #include <direct.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <assert.h>
13 #include <windows.h>
14 #pragma hdrstop
17 * sh_FileFcn --
19 * A function that operates on a file. The pathname is either
20 * absolute or relative to the current directory, and contains
21 * no wildcard characters such as * and ?. Additional arguments
22 * can be passed to the function via the arg pointer.
25 typedef BOOL (*sh_FileFcn)(
26 wchar_t *pathName,
27 WIN32_FIND_DATA *fileData,
28 void *arg);
30 static int shellCp (wchar_t **pArgv);
31 static int shellNsinstall (wchar_t **pArgv);
32 static int shellMkdir (wchar_t **pArgv);
33 static BOOL sh_EnumerateFiles(const wchar_t *pattern, const wchar_t *where,
34 sh_FileFcn fileFcn, void *arg, int *nFiles);
35 static const char *sh_GetLastErrorMessage(void);
36 static BOOL sh_DoCopy(wchar_t *srcFileName, DWORD srcFileAttributes,
37 wchar_t *dstFileName, DWORD dstFileAttributes,
38 int force, int recursive);
40 #define LONGPATH_PREFIX L"\\\\?\\"
41 #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
42 #define STR_LEN(a) (ARRAY_LEN(a) - 1)
44 /* changes all forward slashes in token to backslashes */
45 void changeForwardSlashesToBackSlashes ( wchar_t *arg )
47 if ( arg == NULL )
48 return;
50 while ( *arg ) {
51 if ( *arg == '/' )
52 *arg = '\\';
53 arg++;
57 int wmain(int argc, wchar_t *argv[ ])
59 return shellNsinstall ( argv + 1 );
62 static int
63 shellNsinstall (wchar_t **pArgv)
65 int retVal = 0; /* exit status */
66 int dirOnly = 0; /* 1 if and only if -D is specified */
67 wchar_t **pSrc;
68 wchar_t **pDst;
71 * Process the command-line options. We ignore the
72 * options except for -D. Some options, such as -m,
73 * are followed by an argument. We need to skip the
74 * argument too.
76 while ( *pArgv && **pArgv == '-' ) {
77 wchar_t c = (*pArgv)[1]; /* The char after '-' */
79 if ( c == 'D' ) {
80 dirOnly = 1;
81 } else if ( c == 'm' ) {
82 pArgv++; /* skip the next argument */
84 pArgv++;
87 if ( !dirOnly ) {
88 /* There are files to install. Get source files */
89 if ( *pArgv ) {
90 pSrc = pArgv++;
91 } else {
92 fprintf( stderr, "nsinstall: not enough arguments\n");
93 return 3;
97 /* Get to last token to find destination directory */
98 if ( *pArgv ) {
99 pDst = pArgv++;
100 if ( dirOnly && *pArgv ) {
101 fprintf( stderr, "nsinstall: too many arguments with -D\n");
102 return 3;
104 } else {
105 fprintf( stderr, "nsinstall: not enough arguments\n");
106 return 3;
108 while ( *pArgv )
109 pDst = pArgv++;
111 retVal = shellMkdir ( pDst );
112 if ( retVal )
113 return retVal;
114 if ( !dirOnly )
115 retVal = shellCp ( pSrc );
116 return retVal;
119 static int
120 shellMkdir (wchar_t **pArgv)
122 int retVal = 0; /* assume valid return */
123 wchar_t *arg;
124 wchar_t *pArg;
125 wchar_t path[_MAX_PATH];
126 wchar_t tmpPath[_MAX_PATH];
127 wchar_t *pTmpPath = tmpPath;
129 /* All the options are simply ignored in this implementation */
130 while ( *pArgv && **pArgv == '-' ) {
131 if ( (*pArgv)[1] == 'm' ) {
132 pArgv++; /* skip the next argument (mode) */
134 pArgv++;
137 while ( *pArgv ) {
138 arg = *pArgv;
139 changeForwardSlashesToBackSlashes ( arg );
140 pArg = arg;
141 pTmpPath = tmpPath;
142 while ( 1 ) {
143 /* create part of path */
144 while ( *pArg ) {
145 *pTmpPath++ = *pArg++;
146 if ( *pArg == '\\' )
147 break;
149 *pTmpPath = '\0';
151 /* check if directory already exists */
152 _wgetcwd ( path, _MAX_PATH );
153 if ( _wchdir ( tmpPath ) != -1 ) {
154 _wchdir ( path );
155 } else {
156 if ( _wmkdir ( tmpPath ) == -1 ) {
157 printf ( "%ls: ", tmpPath );
158 perror ( "Could not create the directory" );
159 retVal = 3;
160 break;
163 if ( *pArg == '\0' ) /* complete path? */
164 break;
165 /* loop for next directory */
168 pArgv++;
170 return retVal;
173 static const char *
174 sh_GetLastErrorMessage()
176 static char buf[128];
178 FormatMessageA(
179 FORMAT_MESSAGE_FROM_SYSTEM,
180 NULL,
181 GetLastError(),
182 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* default language */
183 buf,
184 sizeof(buf),
185 NULL
187 return buf;
191 * struct sh_FileData --
193 * A pointer to the sh_FileData structure is passed into sh_RecordFileData,
194 * which will fill in the fields.
197 struct sh_FileData {
198 wchar_t pathName[_MAX_PATH];
199 DWORD dwFileAttributes;
203 * sh_RecordFileData --
205 * Record the pathname and attributes of the file in
206 * the sh_FileData structure pointed to by arg.
208 * Always return TRUE (successful completion).
210 * This function is intended to be passed into sh_EnumerateFiles
211 * to see if a certain pattern expands to exactly one file/directory,
212 * and if so, record its pathname and attributes.
215 static BOOL
216 sh_RecordFileData(wchar_t *pathName, WIN32_FIND_DATA *findData, void *arg)
218 struct sh_FileData *fData = (struct sh_FileData *) arg;
220 wcscpy(fData->pathName, pathName);
221 fData->dwFileAttributes = findData->dwFileAttributes;
222 return TRUE;
225 static BOOL
226 sh_DoCopy(wchar_t *srcFileName,
227 DWORD srcFileAttributes,
228 wchar_t *dstFileName,
229 DWORD dstFileAttributes,
230 int force,
231 int recursive
234 if (dstFileAttributes != 0xFFFFFFFF) {
235 if ((dstFileAttributes & FILE_ATTRIBUTE_READONLY) && force) {
236 dstFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
237 SetFileAttributes(dstFileName, dstFileAttributes);
241 if (srcFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
242 fprintf(stderr, "nsinstall: %ls is a directory\n",
243 srcFileName);
244 return FALSE;
245 } else {
246 DWORD r;
247 wchar_t longSrc[1004] = LONGPATH_PREFIX;
248 wchar_t longDst[1004] = LONGPATH_PREFIX;
249 r = GetFullPathName(srcFileName, 1000, longSrc + STR_LEN(LONGPATH_PREFIX), NULL);
250 if (!r) {
251 fprintf(stderr, "nsinstall: couldn't get full path of %ls: %s\n",
252 srcFileName, sh_GetLastErrorMessage());
253 return FALSE;
255 r = GetFullPathName(dstFileName, 1000, longDst + ARRAY_LEN(LONGPATH_PREFIX) - 1, NULL);
256 if (!r) {
257 fprintf(stderr, "nsinstall: couldn't get full path of %ls: %s\n",
258 dstFileName, sh_GetLastErrorMessage());
259 return FALSE;
262 if (!CopyFile(longSrc, longDst, FALSE)) {
263 fprintf(stderr, "nsinstall: cannot copy %ls to %ls: %s\n",
264 srcFileName, dstFileName, sh_GetLastErrorMessage());
265 return FALSE;
268 return TRUE;
272 * struct sh_CpCmdArg --
274 * A pointer to the sh_CpCmdArg structure is passed into sh_CpFileCmd.
275 * The sh_CpCmdArg contains information about the cp command, and
276 * provide a buffer for constructing the destination file name.
279 struct sh_CpCmdArg {
280 int force; /* -f option, ok to overwrite an existing
281 * read-only destination file */
282 int recursive; /* -r or -R option, recursively copy
283 * directories. Note: this field is not used
284 * by nsinstall and should always be 0. */
285 wchar_t *dstFileName; /* a buffer for constructing the destination
286 * file name */
287 wchar_t *dstFileNameMarker; /* points to where in the dstFileName buffer
288 * we should write the file component of the
289 * destination file */
293 * sh_CpFileCmd --
295 * Copy a file to the destination directory
297 * This function is intended to be passed into sh_EnumerateFiles to
298 * copy all the files specified by the pattern to the destination
299 * directory.
301 * Return TRUE if the file is successfully copied, and FALSE otherwise.
304 static BOOL
305 sh_CpFileCmd(wchar_t *pathName, WIN32_FIND_DATA *findData, void *cpArg)
307 BOOL retVal = TRUE;
308 struct sh_CpCmdArg *arg = (struct sh_CpCmdArg *) cpArg;
310 wcscpy(arg->dstFileNameMarker, findData->cFileName);
311 return sh_DoCopy(pathName, findData->dwFileAttributes,
312 arg->dstFileName, GetFileAttributes(arg->dstFileName),
313 arg->force, arg->recursive);
316 static int
317 shellCp (wchar_t **pArgv)
319 int retVal = 0;
320 wchar_t **pSrc;
321 wchar_t **pDst;
322 struct sh_CpCmdArg arg;
323 struct sh_FileData dstData;
324 int dstIsDir = 0;
325 int n;
327 arg.force = 0;
328 arg.recursive = 0;
329 arg.dstFileName = dstData.pathName;
330 arg.dstFileNameMarker = 0;
332 while (*pArgv && **pArgv == '-') {
333 wchar_t *p = *pArgv;
335 while (*(++p)) {
336 if (*p == 'f') {
337 arg.force = 1;
340 pArgv++;
343 /* the first source file */
344 if (*pArgv) {
345 pSrc = pArgv++;
346 } else {
347 fprintf(stderr, "nsinstall: not enough arguments\n");
348 return 3;
351 /* get to the last token to find destination */
352 if (*pArgv) {
353 pDst = pArgv++;
354 } else {
355 fprintf(stderr, "nsinstall: not enough arguments\n");
356 return 3;
358 while (*pArgv) {
359 pDst = pArgv++;
363 * The destination pattern must unambiguously expand to exactly
364 * one file or directory.
367 changeForwardSlashesToBackSlashes(*pDst);
368 sh_EnumerateFiles(*pDst, *pDst, sh_RecordFileData, &dstData, &n);
369 assert(n >= 0);
370 if (n == 1) {
372 * Is the destination a file or directory?
375 if (dstData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
376 dstIsDir = 1;
378 } else if (n > 1) {
379 fprintf(stderr, "nsinstall: %ls: ambiguous destination file "
380 "or directory\n", *pDst);
381 return 3;
382 } else {
384 * n == 0, meaning that destination file or directory does
385 * not exist. In this case the destination file directory
386 * name must be fully specified.
389 wchar_t *p;
391 for (p = *pDst; *p; p++) {
392 if (*p == '*' || *p == '?') {
393 fprintf(stderr, "nsinstall: %ls: No such file or directory\n",
394 *pDst);
395 return 3;
400 * Do not include the trailing \, if any, unless it is a root
401 * directory (\ or X:\).
404 if (p > *pDst && p[-1] == '\\' && p != *pDst + 1 && p[-2] != ':') {
405 p[-1] = '\0';
407 wcscpy(dstData.pathName, *pDst);
408 dstData.dwFileAttributes = 0xFFFFFFFF;
412 * If there are two or more source files, the destination has
413 * to be a directory.
416 if (pDst - pSrc > 1 && !dstIsDir) {
417 fprintf(stderr, "nsinstall: cannot copy more than"
418 " one file to the same destination file\n");
419 return 3;
422 if (dstIsDir) {
423 arg.dstFileNameMarker = arg.dstFileName + wcslen(arg.dstFileName);
426 * Now arg.dstFileNameMarker is pointing to the null byte at the
427 * end of string. We want to make sure that there is a \ at the
428 * end of string, and arg.dstFileNameMarker should point right
429 * after that \.
432 if (arg.dstFileNameMarker[-1] != '\\') {
433 *(arg.dstFileNameMarker++) = '\\';
437 if (!dstIsDir) {
438 struct sh_FileData srcData;
440 assert(pDst - pSrc == 1);
441 changeForwardSlashesToBackSlashes(*pSrc);
442 sh_EnumerateFiles(*pSrc, *pSrc, sh_RecordFileData, &srcData, &n);
443 if (n == 0) {
444 fprintf(stderr, "nsinstall: %ls: No such file or directory\n",
445 *pSrc);
446 retVal = 3;
447 } else if (n > 1) {
448 fprintf(stderr, "nsinstall: cannot copy more than one file or "
449 "directory to the same destination\n");
450 retVal = 3;
451 } else {
452 assert(n == 1);
453 if (sh_DoCopy(srcData.pathName, srcData.dwFileAttributes,
454 dstData.pathName, dstData.dwFileAttributes,
455 arg.force, arg.recursive) == FALSE) {
456 retVal = 3;
459 return retVal;
462 for ( ; *pSrc != *pDst; pSrc++) {
463 BOOL rv;
465 changeForwardSlashesToBackSlashes(*pSrc);
466 rv = sh_EnumerateFiles(*pSrc, *pSrc, sh_CpFileCmd, &arg, &n);
467 if (rv == FALSE) {
468 retVal = 3;
469 } else {
470 if (n == 0) {
471 fprintf(stderr, "nsinstall: %ls: No such file or directory\n",
472 *pSrc);
473 retVal = 3;
478 return retVal;
482 * sh_EnumerateFiles --
484 * Enumerate all the files in the specified pattern, which is a pathname
485 * containing possibly wildcard characters such as * and ?. fileFcn
486 * is called on each file, passing the expanded file name, a pointer
487 * to the file's WIN32_FILE_DATA, and the arg pointer.
489 * It is assumed that there are no wildcard characters before the
490 * character pointed to by 'where'.
492 * On return, *nFiles stores the number of files enumerated. *nFiles is
493 * set to this number whether sh_EnumerateFiles or 'fileFcn' succeeds
494 * or not.
496 * Return TRUE if the files are successfully enumerated and all
497 * 'fileFcn' invocations succeeded. Return FALSE if something went
498 * wrong.
501 static BOOL sh_EnumerateFiles(
502 const wchar_t *pattern,
503 const wchar_t *where,
504 sh_FileFcn fileFcn,
505 void *arg,
506 int *nFiles
509 WIN32_FIND_DATA fileData;
510 HANDLE hSearch;
511 const wchar_t *src;
512 wchar_t *dst;
513 wchar_t fileName[_MAX_PATH];
514 wchar_t *fileNameMarker = fileName;
515 wchar_t *oldFileNameMarker;
516 BOOL hasWildcard = FALSE;
517 BOOL retVal = TRUE;
518 BOOL patternEndsInDotStar = FALSE;
519 BOOL patternEndsInDot = FALSE; /* a special case of
520 * patternEndsInDotStar */
521 int numDotsInPattern;
522 int len;
525 * Windows expands patterns ending in ".", ".*", ".**", etc.
526 * differently from the glob expansion on Unix. For example,
527 * both "foo." and "foo.*" match "foo", and "*.*" matches
528 * everything, including filenames with no dots. So we need
529 * to throw away extra files returned by the FindNextFile()
530 * function. We require that a matched filename have at least
531 * the number of dots in the pattern.
533 len = wcslen(pattern);
534 if (len >= 2) {
535 /* Start from the end of pattern and go backward */
536 const wchar_t *p = &pattern[len - 1];
538 /* We can have zero or more *'s */
539 while (p >= pattern && *p == '*') {
540 p--;
542 if (p >= pattern && *p == '.') {
543 patternEndsInDotStar = TRUE;
544 if (p == &pattern[len - 1]) {
545 patternEndsInDot = TRUE;
547 p--;
548 numDotsInPattern = 1;
549 while (p >= pattern && *p != '\\') {
550 if (*p == '.') {
551 numDotsInPattern++;
553 p--;
558 *nFiles = 0;
561 * Copy pattern to fileName, but only up to and not including
562 * the first \ after the first wildcard letter.
564 * Make fileNameMarker point to one of the following:
565 * - the start of fileName, if fileName does not contain any \.
566 * - right after the \ before the first wildcard letter, if there is
567 * a wildcard character.
568 * - right after the last \, if there is no wildcard character.
571 dst = fileName;
572 src = pattern;
573 while (src < where) {
574 if (*src == '\\') {
575 oldFileNameMarker = fileNameMarker;
576 fileNameMarker = dst + 1;
578 *(dst++) = *(src++);
581 while (*src && *src != '*' && *src != '?') {
582 if (*src == '\\') {
583 oldFileNameMarker = fileNameMarker;
584 fileNameMarker = dst + 1;
586 *(dst++) = *(src++);
589 if (*src) {
591 * Must have seen the first wildcard letter
594 hasWildcard = TRUE;
595 while (*src && *src != '\\') {
596 *(dst++) = *(src++);
600 /* Now src points to either null or \ */
602 assert(*src == '\0' || *src == '\\');
603 assert(hasWildcard || *src == '\0');
604 *dst = '\0';
607 * If the pattern does not contain any wildcard characters, then
608 * we don't need to go the FindFirstFile route.
611 if (!hasWildcard) {
613 * See if it is the root directory, \, or X:\.
616 assert(!wcscmp(fileName, pattern));
617 assert(wcslen(fileName) >= 1);
618 if (dst[-1] == '\\' && (dst == fileName + 1 || dst[-2] == ':')) {
619 fileData.cFileName[0] = '\0';
620 } else {
622 * Do not include the trailing \, if any
625 if (dst[-1] == '\\') {
626 assert(*fileNameMarker == '\0');
627 dst[-1] = '\0';
628 fileNameMarker = oldFileNameMarker;
630 wcscpy(fileData.cFileName, fileNameMarker);
632 fileData.dwFileAttributes = GetFileAttributes(fileName);
633 if (fileData.dwFileAttributes == 0xFFFFFFFF) {
634 return TRUE;
636 *nFiles = 1;
637 return (*fileFcn)(fileName, &fileData, arg);
640 hSearch = FindFirstFile(fileName, &fileData);
641 if (hSearch == INVALID_HANDLE_VALUE) {
642 return retVal;
645 do {
646 if (!wcscmp(fileData.cFileName, L".")
647 || !wcscmp(fileData.cFileName, L"..")) {
649 * Skip over . and ..
652 continue;
655 if (patternEndsInDotStar) {
656 int nDots = 0;
657 wchar_t *p = fileData.cFileName;
658 while (*p) {
659 if (*p == '.') {
660 nDots++;
662 p++;
664 /* Now p points to the null byte at the end of file name */
665 if (patternEndsInDot && (p == fileData.cFileName
666 || p[-1] != '.')) {
668 * File name does not end in dot. Skip this file.
669 * Note: windows file name probably cannot end in dot,
670 * but we do this check anyway.
672 continue;
674 if (nDots < numDotsInPattern) {
676 * Not enough dots in file name. Must be an extra
677 * file in matching .* pattern. Skip this file.
679 continue;
683 wcscpy(fileNameMarker, fileData.cFileName);
684 if (*src && *(src + 1)) {
686 * More to go. Recurse.
689 int n;
691 assert(*src == '\\');
692 where = fileName + wcslen(fileName);
693 wcscat(fileName, src);
694 sh_EnumerateFiles(fileName, where, fileFcn, arg, &n);
695 *nFiles += n;
696 } else {
697 assert(wcschr(fileName, '*') == NULL);
698 assert(wcschr(fileName, '?') == NULL);
699 (*nFiles)++;
700 if ((*fileFcn)(fileName, &fileData, arg) == FALSE) {
701 retVal = FALSE;
704 } while (FindNextFile(hSearch, &fileData));
706 FindClose(hSearch);
707 return retVal;