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
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
)(
27 WIN32_FIND_DATA
*fileData
,
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
)
57 int wmain(int argc
, wchar_t *argv
[ ])
59 return shellNsinstall ( argv
+ 1 );
63 shellNsinstall (wchar_t **pArgv
)
65 int retVal
= 0; /* exit status */
66 int dirOnly
= 0; /* 1 if and only if -D is specified */
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
76 while ( *pArgv
&& **pArgv
== '-' ) {
77 wchar_t c
= (*pArgv
)[1]; /* The char after '-' */
81 } else if ( c
== 'm' ) {
82 pArgv
++; /* skip the next argument */
88 /* There are files to install. Get source files */
92 fprintf( stderr
, "nsinstall: not enough arguments\n");
97 /* Get to last token to find destination directory */
100 if ( dirOnly
&& *pArgv
) {
101 fprintf( stderr
, "nsinstall: too many arguments with -D\n");
105 fprintf( stderr
, "nsinstall: not enough arguments\n");
111 retVal
= shellMkdir ( pDst
);
115 retVal
= shellCp ( pSrc
);
120 shellMkdir (wchar_t **pArgv
)
122 int retVal
= 0; /* assume valid return */
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) */
139 changeForwardSlashesToBackSlashes ( arg
);
143 /* create part of path */
145 *pTmpPath
++ = *pArg
++;
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
157 _snprintf(buf
, 2048, "Could not create the directory: %S",
163 // get back to the cwd
166 if ( *pArg
== '\0' ) /* complete path? */
168 /* loop for next directory */
177 sh_GetLastErrorMessage()
179 static char buf
[128];
182 FORMAT_MESSAGE_FROM_SYSTEM
,
185 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), /* default language */
194 * struct sh_FileData --
196 * A pointer to the sh_FileData structure is passed into sh_RecordFileData,
197 * which will fill in the fields.
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.
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
;
229 sh_DoCopy(wchar_t *srcFileName
,
230 DWORD srcFileAttributes
,
231 wchar_t *dstFileName
,
232 DWORD dstFileAttributes
,
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",
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
);
254 fprintf(stderr
, "nsinstall: couldn't get full path of %ls: %s\n",
255 srcFileName
, sh_GetLastErrorMessage());
258 r
= GetFullPathName(dstFileName
, 1000, longDst
+ ARRAY_LEN(LONGPATH_PREFIX
) - 1, NULL
);
260 fprintf(stderr
, "nsinstall: couldn't get full path of %ls: %s\n",
261 dstFileName
, sh_GetLastErrorMessage());
265 if (!CopyFile(longSrc
, longDst
, FALSE
)) {
266 fprintf(stderr
, "nsinstall: cannot copy %ls to %ls: %s\n",
267 srcFileName
, dstFileName
, sh_GetLastErrorMessage());
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.
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
290 wchar_t *dstFileNameMarker
; /* points to where in the dstFileName buffer
291 * we should write the file component of the
292 * destination file */
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
304 * Return TRUE if the file is successfully copied, and FALSE otherwise.
308 sh_CpFileCmd(wchar_t *pathName
, WIN32_FIND_DATA
*findData
, void *cpArg
)
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
);
320 shellCp (wchar_t **pArgv
)
325 struct sh_CpCmdArg arg
;
326 struct sh_FileData dstData
;
332 arg
.dstFileName
= dstData
.pathName
;
333 arg
.dstFileNameMarker
= 0;
335 while (*pArgv
&& **pArgv
== '-') {
346 /* the first source file */
350 fprintf(stderr
, "nsinstall: not enough arguments\n");
354 /* get to the last token to find destination */
358 fprintf(stderr
, "nsinstall: not enough arguments\n");
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
);
375 * Is the destination a file or directory?
378 if (dstData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
382 fprintf(stderr
, "nsinstall: %ls: ambiguous destination file "
383 "or directory\n", *pDst
);
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.
394 for (p
= *pDst
; *p
; p
++) {
395 if (*p
== '*' || *p
== '?') {
396 fprintf(stderr
, "nsinstall: %ls: No such file or directory\n",
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] != ':') {
410 wcscpy(dstData
.pathName
, *pDst
);
411 dstData
.dwFileAttributes
= 0xFFFFFFFF;
415 * If there are two or more source files, the destination has
419 if (pDst
- pSrc
> 1 && !dstIsDir
) {
420 fprintf(stderr
, "nsinstall: cannot copy more than"
421 " one file to the same destination file\n");
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
435 if (arg
.dstFileNameMarker
[-1] != '\\') {
436 *(arg
.dstFileNameMarker
++) = '\\';
441 struct sh_FileData srcData
;
443 assert(pDst
- pSrc
== 1);
444 changeForwardSlashesToBackSlashes(*pSrc
);
445 sh_EnumerateFiles(*pSrc
, *pSrc
, sh_RecordFileData
, &srcData
, &n
);
447 fprintf(stderr
, "nsinstall: %ls: No such file or directory\n",
451 fprintf(stderr
, "nsinstall: cannot copy more than one file or "
452 "directory to the same destination\n");
456 if (sh_DoCopy(srcData
.pathName
, srcData
.dwFileAttributes
,
457 dstData
.pathName
, dstData
.dwFileAttributes
,
458 arg
.force
, arg
.recursive
) == FALSE
) {
465 for ( ; *pSrc
!= *pDst
; pSrc
++) {
468 changeForwardSlashesToBackSlashes(*pSrc
);
469 rv
= sh_EnumerateFiles(*pSrc
, *pSrc
, sh_CpFileCmd
, &arg
, &n
);
474 fprintf(stderr
, "nsinstall: %ls: No such file or directory\n",
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
499 * Return TRUE if the files are successfully enumerated and all
500 * 'fileFcn' invocations succeeded. Return FALSE if something went
504 static BOOL
sh_EnumerateFiles(
505 const wchar_t *pattern
,
506 const wchar_t *where
,
512 WIN32_FIND_DATA fileData
;
516 wchar_t fileName
[_MAX_PATH
];
517 wchar_t *fileNameMarker
= fileName
;
518 wchar_t *oldFileNameMarker
;
519 BOOL hasWildcard
= FALSE
;
521 BOOL patternEndsInDotStar
= FALSE
;
522 BOOL patternEndsInDot
= FALSE
; /* a special case of
523 * patternEndsInDotStar */
524 int numDotsInPattern
;
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
);
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
== '*') {
545 if (p
>= pattern
&& *p
== '.') {
546 patternEndsInDotStar
= TRUE
;
547 if (p
== &pattern
[len
- 1]) {
548 patternEndsInDot
= TRUE
;
551 numDotsInPattern
= 1;
552 while (p
>= pattern
&& *p
!= '\\') {
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.
576 while (src
< where
) {
578 oldFileNameMarker
= fileNameMarker
;
579 fileNameMarker
= dst
+ 1;
584 while (*src
&& *src
!= '*' && *src
!= '?') {
586 oldFileNameMarker
= fileNameMarker
;
587 fileNameMarker
= dst
+ 1;
594 * Must have seen the first wildcard letter
598 while (*src
&& *src
!= '\\') {
603 /* Now src points to either null or \ */
605 assert(*src
== '\0' || *src
== '\\');
606 assert(hasWildcard
|| *src
== '\0');
610 * If the pattern does not contain any wildcard characters, then
611 * we don't need to go the FindFirstFile route.
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';
625 * Do not include the trailing \, if any
628 if (dst
[-1] == '\\') {
629 assert(*fileNameMarker
== '\0');
631 fileNameMarker
= oldFileNameMarker
;
633 wcscpy(fileData
.cFileName
, fileNameMarker
);
635 fileData
.dwFileAttributes
= GetFileAttributes(fileName
);
636 if (fileData
.dwFileAttributes
== 0xFFFFFFFF) {
640 return (*fileFcn
)(fileName
, &fileData
, arg
);
643 hSearch
= FindFirstFile(fileName
, &fileData
);
644 if (hSearch
== INVALID_HANDLE_VALUE
) {
649 if (!wcscmp(fileData
.cFileName
, L
".")
650 || !wcscmp(fileData
.cFileName
, L
"..")) {
658 if (patternEndsInDotStar
) {
660 wchar_t *p
= fileData
.cFileName
;
667 /* Now p points to the null byte at the end of file name */
668 if (patternEndsInDot
&& (p
== fileData
.cFileName
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.
677 if (nDots
< numDotsInPattern
) {
679 * Not enough dots in file name. Must be an extra
680 * file in matching .* pattern. Skip this file.
686 wcscpy(fileNameMarker
, fileData
.cFileName
);
687 if (*src
&& *(src
+ 1)) {
689 * More to go. Recurse.
694 assert(*src
== '\\');
695 where
= fileName
+ wcslen(fileName
);
696 wcscat(fileName
, src
);
697 sh_EnumerateFiles(fileName
, where
, fileFcn
, arg
, &n
);
700 assert(wcschr(fileName
, '*') == NULL
);
701 assert(wcschr(fileName
, '?') == NULL
);
703 if ((*fileFcn
)(fileName
, &fileData
, arg
) == FALSE
) {
707 } while (FindNextFile(hSearch
, &fileData
));