Don't bother tracing some elem sets yet (bug 561939, r=gal).
[mozilla-central.git] / config / nsinstall_win.c
blobd9b723b2c7c5565511e87a8d4edae4fc8f68926a
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 _wmkdir ( tmpPath ) == -1 && // might have hit EEXIST
155 _wchdir ( tmpPath ) == -1) { // so try again
156 char buf[2048];
157 _snprintf(buf, 2048, "Could not create the directory: %S",
158 tmpPath);
159 perror ( buf );
160 retVal = 3;
161 break;
162 } else {
163 // get back to the cwd
164 _wchdir ( path );
166 if ( *pArg == '\0' ) /* complete path? */
167 break;
168 /* loop for next directory */
171 pArgv++;
173 return retVal;
176 static const char *
177 sh_GetLastErrorMessage()
179 static char buf[128];
181 FormatMessageA(
182 FORMAT_MESSAGE_FROM_SYSTEM,
183 NULL,
184 GetLastError(),
185 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* default language */
186 buf,
187 sizeof(buf),
188 NULL
190 return buf;
194 * struct sh_FileData --
196 * A pointer to the sh_FileData structure is passed into sh_RecordFileData,
197 * which will fill in the fields.
200 struct sh_FileData {
201 wchar_t pathName[_MAX_PATH];
202 DWORD dwFileAttributes;
206 * sh_RecordFileData --
208 * Record the pathname and attributes of the file in
209 * the sh_FileData structure pointed to by arg.
211 * Always return TRUE (successful completion).
213 * This function is intended to be passed into sh_EnumerateFiles
214 * to see if a certain pattern expands to exactly one file/directory,
215 * and if so, record its pathname and attributes.
218 static BOOL
219 sh_RecordFileData(wchar_t *pathName, WIN32_FIND_DATA *findData, void *arg)
221 struct sh_FileData *fData = (struct sh_FileData *) arg;
223 wcscpy(fData->pathName, pathName);
224 fData->dwFileAttributes = findData->dwFileAttributes;
225 return TRUE;
228 static BOOL
229 sh_DoCopy(wchar_t *srcFileName,
230 DWORD srcFileAttributes,
231 wchar_t *dstFileName,
232 DWORD dstFileAttributes,
233 int force,
234 int recursive
237 if (dstFileAttributes != 0xFFFFFFFF) {
238 if ((dstFileAttributes & FILE_ATTRIBUTE_READONLY) && force) {
239 dstFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
240 SetFileAttributes(dstFileName, dstFileAttributes);
244 if (srcFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
245 fprintf(stderr, "nsinstall: %ls is a directory\n",
246 srcFileName);
247 return FALSE;
248 } else {
249 DWORD r;
250 wchar_t longSrc[1004] = LONGPATH_PREFIX;
251 wchar_t longDst[1004] = LONGPATH_PREFIX;
252 r = GetFullPathName(srcFileName, 1000, longSrc + STR_LEN(LONGPATH_PREFIX), NULL);
253 if (!r) {
254 fprintf(stderr, "nsinstall: couldn't get full path of %ls: %s\n",
255 srcFileName, sh_GetLastErrorMessage());
256 return FALSE;
258 r = GetFullPathName(dstFileName, 1000, longDst + ARRAY_LEN(LONGPATH_PREFIX) - 1, NULL);
259 if (!r) {
260 fprintf(stderr, "nsinstall: couldn't get full path of %ls: %s\n",
261 dstFileName, sh_GetLastErrorMessage());
262 return FALSE;
265 if (!CopyFile(longSrc, longDst, FALSE)) {
266 fprintf(stderr, "nsinstall: cannot copy %ls to %ls: %s\n",
267 srcFileName, dstFileName, sh_GetLastErrorMessage());
268 return FALSE;
271 return TRUE;
275 * struct sh_CpCmdArg --
277 * A pointer to the sh_CpCmdArg structure is passed into sh_CpFileCmd.
278 * The sh_CpCmdArg contains information about the cp command, and
279 * provide a buffer for constructing the destination file name.
282 struct sh_CpCmdArg {
283 int force; /* -f option, ok to overwrite an existing
284 * read-only destination file */
285 int recursive; /* -r or -R option, recursively copy
286 * directories. Note: this field is not used
287 * by nsinstall and should always be 0. */
288 wchar_t *dstFileName; /* a buffer for constructing the destination
289 * file name */
290 wchar_t *dstFileNameMarker; /* points to where in the dstFileName buffer
291 * we should write the file component of the
292 * destination file */
296 * sh_CpFileCmd --
298 * Copy a file to the destination directory
300 * This function is intended to be passed into sh_EnumerateFiles to
301 * copy all the files specified by the pattern to the destination
302 * directory.
304 * Return TRUE if the file is successfully copied, and FALSE otherwise.
307 static BOOL
308 sh_CpFileCmd(wchar_t *pathName, WIN32_FIND_DATA *findData, void *cpArg)
310 BOOL retVal = TRUE;
311 struct sh_CpCmdArg *arg = (struct sh_CpCmdArg *) cpArg;
313 wcscpy(arg->dstFileNameMarker, findData->cFileName);
314 return sh_DoCopy(pathName, findData->dwFileAttributes,
315 arg->dstFileName, GetFileAttributes(arg->dstFileName),
316 arg->force, arg->recursive);
319 static int
320 shellCp (wchar_t **pArgv)
322 int retVal = 0;
323 wchar_t **pSrc;
324 wchar_t **pDst;
325 struct sh_CpCmdArg arg;
326 struct sh_FileData dstData;
327 int dstIsDir = 0;
328 int n;
330 arg.force = 0;
331 arg.recursive = 0;
332 arg.dstFileName = dstData.pathName;
333 arg.dstFileNameMarker = 0;
335 while (*pArgv && **pArgv == '-') {
336 wchar_t *p = *pArgv;
338 while (*(++p)) {
339 if (*p == 'f') {
340 arg.force = 1;
343 pArgv++;
346 /* the first source file */
347 if (*pArgv) {
348 pSrc = pArgv++;
349 } else {
350 fprintf(stderr, "nsinstall: not enough arguments\n");
351 return 3;
354 /* get to the last token to find destination */
355 if (*pArgv) {
356 pDst = pArgv++;
357 } else {
358 fprintf(stderr, "nsinstall: not enough arguments\n");
359 return 3;
361 while (*pArgv) {
362 pDst = pArgv++;
366 * The destination pattern must unambiguously expand to exactly
367 * one file or directory.
370 changeForwardSlashesToBackSlashes(*pDst);
371 sh_EnumerateFiles(*pDst, *pDst, sh_RecordFileData, &dstData, &n);
372 assert(n >= 0);
373 if (n == 1) {
375 * Is the destination a file or directory?
378 if (dstData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
379 dstIsDir = 1;
381 } else if (n > 1) {
382 fprintf(stderr, "nsinstall: %ls: ambiguous destination file "
383 "or directory\n", *pDst);
384 return 3;
385 } else {
387 * n == 0, meaning that destination file or directory does
388 * not exist. In this case the destination file directory
389 * name must be fully specified.
392 wchar_t *p;
394 for (p = *pDst; *p; p++) {
395 if (*p == '*' || *p == '?') {
396 fprintf(stderr, "nsinstall: %ls: No such file or directory\n",
397 *pDst);
398 return 3;
403 * Do not include the trailing \, if any, unless it is a root
404 * directory (\ or X:\).
407 if (p > *pDst && p[-1] == '\\' && p != *pDst + 1 && p[-2] != ':') {
408 p[-1] = '\0';
410 wcscpy(dstData.pathName, *pDst);
411 dstData.dwFileAttributes = 0xFFFFFFFF;
415 * If there are two or more source files, the destination has
416 * to be a directory.
419 if (pDst - pSrc > 1 && !dstIsDir) {
420 fprintf(stderr, "nsinstall: cannot copy more than"
421 " one file to the same destination file\n");
422 return 3;
425 if (dstIsDir) {
426 arg.dstFileNameMarker = arg.dstFileName + wcslen(arg.dstFileName);
429 * Now arg.dstFileNameMarker is pointing to the null byte at the
430 * end of string. We want to make sure that there is a \ at the
431 * end of string, and arg.dstFileNameMarker should point right
432 * after that \.
435 if (arg.dstFileNameMarker[-1] != '\\') {
436 *(arg.dstFileNameMarker++) = '\\';
440 if (!dstIsDir) {
441 struct sh_FileData srcData;
443 assert(pDst - pSrc == 1);
444 changeForwardSlashesToBackSlashes(*pSrc);
445 sh_EnumerateFiles(*pSrc, *pSrc, sh_RecordFileData, &srcData, &n);
446 if (n == 0) {
447 fprintf(stderr, "nsinstall: %ls: No such file or directory\n",
448 *pSrc);
449 retVal = 3;
450 } else if (n > 1) {
451 fprintf(stderr, "nsinstall: cannot copy more than one file or "
452 "directory to the same destination\n");
453 retVal = 3;
454 } else {
455 assert(n == 1);
456 if (sh_DoCopy(srcData.pathName, srcData.dwFileAttributes,
457 dstData.pathName, dstData.dwFileAttributes,
458 arg.force, arg.recursive) == FALSE) {
459 retVal = 3;
462 return retVal;
465 for ( ; *pSrc != *pDst; pSrc++) {
466 BOOL rv;
468 changeForwardSlashesToBackSlashes(*pSrc);
469 rv = sh_EnumerateFiles(*pSrc, *pSrc, sh_CpFileCmd, &arg, &n);
470 if (rv == FALSE) {
471 retVal = 3;
472 } else {
473 if (n == 0) {
474 fprintf(stderr, "nsinstall: %ls: No such file or directory\n",
475 *pSrc);
476 retVal = 3;
481 return retVal;
485 * sh_EnumerateFiles --
487 * Enumerate all the files in the specified pattern, which is a pathname
488 * containing possibly wildcard characters such as * and ?. fileFcn
489 * is called on each file, passing the expanded file name, a pointer
490 * to the file's WIN32_FILE_DATA, and the arg pointer.
492 * It is assumed that there are no wildcard characters before the
493 * character pointed to by 'where'.
495 * On return, *nFiles stores the number of files enumerated. *nFiles is
496 * set to this number whether sh_EnumerateFiles or 'fileFcn' succeeds
497 * or not.
499 * Return TRUE if the files are successfully enumerated and all
500 * 'fileFcn' invocations succeeded. Return FALSE if something went
501 * wrong.
504 static BOOL sh_EnumerateFiles(
505 const wchar_t *pattern,
506 const wchar_t *where,
507 sh_FileFcn fileFcn,
508 void *arg,
509 int *nFiles
512 WIN32_FIND_DATA fileData;
513 HANDLE hSearch;
514 const wchar_t *src;
515 wchar_t *dst;
516 wchar_t fileName[_MAX_PATH];
517 wchar_t *fileNameMarker = fileName;
518 wchar_t *oldFileNameMarker;
519 BOOL hasWildcard = FALSE;
520 BOOL retVal = TRUE;
521 BOOL patternEndsInDotStar = FALSE;
522 BOOL patternEndsInDot = FALSE; /* a special case of
523 * patternEndsInDotStar */
524 int numDotsInPattern;
525 int len;
528 * Windows expands patterns ending in ".", ".*", ".**", etc.
529 * differently from the glob expansion on Unix. For example,
530 * both "foo." and "foo.*" match "foo", and "*.*" matches
531 * everything, including filenames with no dots. So we need
532 * to throw away extra files returned by the FindNextFile()
533 * function. We require that a matched filename have at least
534 * the number of dots in the pattern.
536 len = wcslen(pattern);
537 if (len >= 2) {
538 /* Start from the end of pattern and go backward */
539 const wchar_t *p = &pattern[len - 1];
541 /* We can have zero or more *'s */
542 while (p >= pattern && *p == '*') {
543 p--;
545 if (p >= pattern && *p == '.') {
546 patternEndsInDotStar = TRUE;
547 if (p == &pattern[len - 1]) {
548 patternEndsInDot = TRUE;
550 p--;
551 numDotsInPattern = 1;
552 while (p >= pattern && *p != '\\') {
553 if (*p == '.') {
554 numDotsInPattern++;
556 p--;
561 *nFiles = 0;
564 * Copy pattern to fileName, but only up to and not including
565 * the first \ after the first wildcard letter.
567 * Make fileNameMarker point to one of the following:
568 * - the start of fileName, if fileName does not contain any \.
569 * - right after the \ before the first wildcard letter, if there is
570 * a wildcard character.
571 * - right after the last \, if there is no wildcard character.
574 dst = fileName;
575 src = pattern;
576 while (src < where) {
577 if (*src == '\\') {
578 oldFileNameMarker = fileNameMarker;
579 fileNameMarker = dst + 1;
581 *(dst++) = *(src++);
584 while (*src && *src != '*' && *src != '?') {
585 if (*src == '\\') {
586 oldFileNameMarker = fileNameMarker;
587 fileNameMarker = dst + 1;
589 *(dst++) = *(src++);
592 if (*src) {
594 * Must have seen the first wildcard letter
597 hasWildcard = TRUE;
598 while (*src && *src != '\\') {
599 *(dst++) = *(src++);
603 /* Now src points to either null or \ */
605 assert(*src == '\0' || *src == '\\');
606 assert(hasWildcard || *src == '\0');
607 *dst = '\0';
610 * If the pattern does not contain any wildcard characters, then
611 * we don't need to go the FindFirstFile route.
614 if (!hasWildcard) {
616 * See if it is the root directory, \, or X:\.
619 assert(!wcscmp(fileName, pattern));
620 assert(wcslen(fileName) >= 1);
621 if (dst[-1] == '\\' && (dst == fileName + 1 || dst[-2] == ':')) {
622 fileData.cFileName[0] = '\0';
623 } else {
625 * Do not include the trailing \, if any
628 if (dst[-1] == '\\') {
629 assert(*fileNameMarker == '\0');
630 dst[-1] = '\0';
631 fileNameMarker = oldFileNameMarker;
633 wcscpy(fileData.cFileName, fileNameMarker);
635 fileData.dwFileAttributes = GetFileAttributes(fileName);
636 if (fileData.dwFileAttributes == 0xFFFFFFFF) {
637 return TRUE;
639 *nFiles = 1;
640 return (*fileFcn)(fileName, &fileData, arg);
643 hSearch = FindFirstFile(fileName, &fileData);
644 if (hSearch == INVALID_HANDLE_VALUE) {
645 return retVal;
648 do {
649 if (!wcscmp(fileData.cFileName, L".")
650 || !wcscmp(fileData.cFileName, L"..")) {
652 * Skip over . and ..
655 continue;
658 if (patternEndsInDotStar) {
659 int nDots = 0;
660 wchar_t *p = fileData.cFileName;
661 while (*p) {
662 if (*p == '.') {
663 nDots++;
665 p++;
667 /* Now p points to the null byte at the end of file name */
668 if (patternEndsInDot && (p == fileData.cFileName
669 || p[-1] != '.')) {
671 * File name does not end in dot. Skip this file.
672 * Note: windows file name probably cannot end in dot,
673 * but we do this check anyway.
675 continue;
677 if (nDots < numDotsInPattern) {
679 * Not enough dots in file name. Must be an extra
680 * file in matching .* pattern. Skip this file.
682 continue;
686 wcscpy(fileNameMarker, fileData.cFileName);
687 if (*src && *(src + 1)) {
689 * More to go. Recurse.
692 int n;
694 assert(*src == '\\');
695 where = fileName + wcslen(fileName);
696 wcscat(fileName, src);
697 sh_EnumerateFiles(fileName, where, fileFcn, arg, &n);
698 *nFiles += n;
699 } else {
700 assert(wcschr(fileName, '*') == NULL);
701 assert(wcschr(fileName, '?') == NULL);
702 (*nFiles)++;
703 if ((*fileFcn)(fileName, &fileData, arg) == FALSE) {
704 retVal = FALSE;
707 } while (FindNextFile(hSearch, &fileData));
709 FindClose(hSearch);
710 return retVal;