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 ) {
156 if ( _wmkdir ( tmpPath
) == -1 ) {
157 printf ( "%ls: ", tmpPath
);
158 perror ( "Could not create the directory" );
163 if ( *pArg
== '\0' ) /* complete path? */
165 /* loop for next directory */
174 sh_GetLastErrorMessage()
176 static char buf
[128];
179 FORMAT_MESSAGE_FROM_SYSTEM
,
182 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), /* default language */
191 * struct sh_FileData --
193 * A pointer to the sh_FileData structure is passed into sh_RecordFileData,
194 * which will fill in the fields.
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.
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
;
226 sh_DoCopy(wchar_t *srcFileName
,
227 DWORD srcFileAttributes
,
228 wchar_t *dstFileName
,
229 DWORD dstFileAttributes
,
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",
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
);
251 fprintf(stderr
, "nsinstall: couldn't get full path of %ls: %s\n",
252 srcFileName
, sh_GetLastErrorMessage());
255 r
= GetFullPathName(dstFileName
, 1000, longDst
+ ARRAY_LEN(LONGPATH_PREFIX
) - 1, NULL
);
257 fprintf(stderr
, "nsinstall: couldn't get full path of %ls: %s\n",
258 dstFileName
, sh_GetLastErrorMessage());
262 if (!CopyFile(longSrc
, longDst
, FALSE
)) {
263 fprintf(stderr
, "nsinstall: cannot copy %ls to %ls: %s\n",
264 srcFileName
, dstFileName
, sh_GetLastErrorMessage());
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.
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
287 wchar_t *dstFileNameMarker
; /* points to where in the dstFileName buffer
288 * we should write the file component of the
289 * destination file */
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
301 * Return TRUE if the file is successfully copied, and FALSE otherwise.
305 sh_CpFileCmd(wchar_t *pathName
, WIN32_FIND_DATA
*findData
, void *cpArg
)
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
);
317 shellCp (wchar_t **pArgv
)
322 struct sh_CpCmdArg arg
;
323 struct sh_FileData dstData
;
329 arg
.dstFileName
= dstData
.pathName
;
330 arg
.dstFileNameMarker
= 0;
332 while (*pArgv
&& **pArgv
== '-') {
343 /* the first source file */
347 fprintf(stderr
, "nsinstall: not enough arguments\n");
351 /* get to the last token to find destination */
355 fprintf(stderr
, "nsinstall: not enough arguments\n");
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
);
372 * Is the destination a file or directory?
375 if (dstData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
379 fprintf(stderr
, "nsinstall: %ls: ambiguous destination file "
380 "or directory\n", *pDst
);
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.
391 for (p
= *pDst
; *p
; p
++) {
392 if (*p
== '*' || *p
== '?') {
393 fprintf(stderr
, "nsinstall: %ls: No such file or directory\n",
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] != ':') {
407 wcscpy(dstData
.pathName
, *pDst
);
408 dstData
.dwFileAttributes
= 0xFFFFFFFF;
412 * If there are two or more source files, the destination has
416 if (pDst
- pSrc
> 1 && !dstIsDir
) {
417 fprintf(stderr
, "nsinstall: cannot copy more than"
418 " one file to the same destination file\n");
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
432 if (arg
.dstFileNameMarker
[-1] != '\\') {
433 *(arg
.dstFileNameMarker
++) = '\\';
438 struct sh_FileData srcData
;
440 assert(pDst
- pSrc
== 1);
441 changeForwardSlashesToBackSlashes(*pSrc
);
442 sh_EnumerateFiles(*pSrc
, *pSrc
, sh_RecordFileData
, &srcData
, &n
);
444 fprintf(stderr
, "nsinstall: %ls: No such file or directory\n",
448 fprintf(stderr
, "nsinstall: cannot copy more than one file or "
449 "directory to the same destination\n");
453 if (sh_DoCopy(srcData
.pathName
, srcData
.dwFileAttributes
,
454 dstData
.pathName
, dstData
.dwFileAttributes
,
455 arg
.force
, arg
.recursive
) == FALSE
) {
462 for ( ; *pSrc
!= *pDst
; pSrc
++) {
465 changeForwardSlashesToBackSlashes(*pSrc
);
466 rv
= sh_EnumerateFiles(*pSrc
, *pSrc
, sh_CpFileCmd
, &arg
, &n
);
471 fprintf(stderr
, "nsinstall: %ls: No such file or directory\n",
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
496 * Return TRUE if the files are successfully enumerated and all
497 * 'fileFcn' invocations succeeded. Return FALSE if something went
501 static BOOL
sh_EnumerateFiles(
502 const wchar_t *pattern
,
503 const wchar_t *where
,
509 WIN32_FIND_DATA fileData
;
513 wchar_t fileName
[_MAX_PATH
];
514 wchar_t *fileNameMarker
= fileName
;
515 wchar_t *oldFileNameMarker
;
516 BOOL hasWildcard
= FALSE
;
518 BOOL patternEndsInDotStar
= FALSE
;
519 BOOL patternEndsInDot
= FALSE
; /* a special case of
520 * patternEndsInDotStar */
521 int numDotsInPattern
;
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
);
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
== '*') {
542 if (p
>= pattern
&& *p
== '.') {
543 patternEndsInDotStar
= TRUE
;
544 if (p
== &pattern
[len
- 1]) {
545 patternEndsInDot
= TRUE
;
548 numDotsInPattern
= 1;
549 while (p
>= pattern
&& *p
!= '\\') {
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.
573 while (src
< where
) {
575 oldFileNameMarker
= fileNameMarker
;
576 fileNameMarker
= dst
+ 1;
581 while (*src
&& *src
!= '*' && *src
!= '?') {
583 oldFileNameMarker
= fileNameMarker
;
584 fileNameMarker
= dst
+ 1;
591 * Must have seen the first wildcard letter
595 while (*src
&& *src
!= '\\') {
600 /* Now src points to either null or \ */
602 assert(*src
== '\0' || *src
== '\\');
603 assert(hasWildcard
|| *src
== '\0');
607 * If the pattern does not contain any wildcard characters, then
608 * we don't need to go the FindFirstFile route.
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';
622 * Do not include the trailing \, if any
625 if (dst
[-1] == '\\') {
626 assert(*fileNameMarker
== '\0');
628 fileNameMarker
= oldFileNameMarker
;
630 wcscpy(fileData
.cFileName
, fileNameMarker
);
632 fileData
.dwFileAttributes
= GetFileAttributes(fileName
);
633 if (fileData
.dwFileAttributes
== 0xFFFFFFFF) {
637 return (*fileFcn
)(fileName
, &fileData
, arg
);
640 hSearch
= FindFirstFile(fileName
, &fileData
);
641 if (hSearch
== INVALID_HANDLE_VALUE
) {
646 if (!wcscmp(fileData
.cFileName
, L
".")
647 || !wcscmp(fileData
.cFileName
, L
"..")) {
655 if (patternEndsInDotStar
) {
657 wchar_t *p
= fileData
.cFileName
;
664 /* Now p points to the null byte at the end of file name */
665 if (patternEndsInDot
&& (p
== fileData
.cFileName
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.
674 if (nDots
< numDotsInPattern
) {
676 * Not enough dots in file name. Must be an extra
677 * file in matching .* pattern. Skip this file.
683 wcscpy(fileNameMarker
, fileData
.cFileName
);
684 if (*src
&& *(src
+ 1)) {
686 * More to go. Recurse.
691 assert(*src
== '\\');
692 where
= fileName
+ wcslen(fileName
);
693 wcscat(fileName
, src
);
694 sh_EnumerateFiles(fileName
, where
, fileFcn
, arg
, &n
);
697 assert(wcschr(fileName
, '*') == NULL
);
698 assert(wcschr(fileName
, '?') == NULL
);
700 if ((*fileFcn
)(fileName
, &fileData
, arg
) == FALSE
) {
704 } while (FindNextFile(hSearch
, &fileData
));