/* * The nsinstall command for Win32 * * Our gmake makefiles use the nsinstall command to create the * object directories or installing headers and libs. This code was originally * taken from shmsdos.c */ #include #include #include #include #include #pragma hdrstop /* * sh_FileFcn -- * * A function that operates on a file. The pathname is either * absolute or relative to the current directory, and contains * no wildcard characters such as * and ?. Additional arguments * can be passed to the function via the arg pointer. */ typedef BOOL (*sh_FileFcn)( wchar_t *pathName, WIN32_FIND_DATA *fileData, void *arg); static int shellCp (wchar_t **pArgv); static int shellNsinstall (wchar_t **pArgv); static int shellMkdir (wchar_t **pArgv); static BOOL sh_EnumerateFiles(const wchar_t *pattern, const wchar_t *where, sh_FileFcn fileFcn, void *arg, int *nFiles); static const char *sh_GetLastErrorMessage(void); static BOOL sh_DoCopy(wchar_t *srcFileName, DWORD srcFileAttributes, wchar_t *dstFileName, DWORD dstFileAttributes, int force, int recursive); #define LONGPATH_PREFIX L"\\\\?\\" #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define STR_LEN(a) (ARRAY_LEN(a) - 1) #ifdef __MINGW32__ /* MingW currently does not implement a wide version of the startup routines. Workaround is to implement something like it ourselves. */ #include int wmain(int argc, WCHAR **argv); int main(int argc, char **argv) { int result; wchar_t *commandLine = GetCommandLineW(); int argcw = 0; wchar_t **_argvw = CommandLineToArgvW( commandLine, &argcw ); wchar_t *argvw[argcw + 1]; int i; if (!_argvw) return 127; /* CommandLineToArgvW doesn't output the ending NULL so we have to manually add it on */ for ( i = 0; i < argcw; i++ ) argvw[i] = _argvw[i]; argvw[argcw] = NULL; result = wmain(argcw, argvw); LocalFree(_argvw); return result; } #endif /* __MINGW32__ */ /* changes all forward slashes in token to backslashes */ void changeForwardSlashesToBackSlashes ( wchar_t *arg ) { if ( arg == NULL ) return; while ( *arg ) { if ( *arg == '/' ) *arg = '\\'; arg++; } } int wmain(int argc, wchar_t *argv[ ]) { return shellNsinstall ( argv + 1 ); } static int shellNsinstall (wchar_t **pArgv) { int retVal = 0; /* exit status */ int dirOnly = 0; /* 1 if and only if -D is specified */ wchar_t **pSrc; wchar_t **pDst; /* * Process the command-line options. We ignore the * options except for -D. Some options, such as -m, * are followed by an argument. We need to skip the * argument too. */ while ( *pArgv && **pArgv == '-' ) { wchar_t c = (*pArgv)[1]; /* The char after '-' */ if ( c == 'D' ) { dirOnly = 1; } else if ( c == 'm' ) { pArgv++; /* skip the next argument */ } pArgv++; } if ( !dirOnly ) { /* There are files to install. Get source files */ if ( *pArgv ) { pSrc = pArgv++; } else { fprintf( stderr, "nsinstall: not enough arguments\n"); return 3; } } /* Get to last token to find destination directory */ if ( *pArgv ) { pDst = pArgv++; if ( dirOnly && *pArgv ) { fprintf( stderr, "nsinstall: too many arguments with -D\n"); return 3; } } else { fprintf( stderr, "nsinstall: not enough arguments\n"); return 3; } while ( *pArgv ) pDst = pArgv++; retVal = shellMkdir ( pDst ); if ( retVal ) return retVal; if ( !dirOnly ) retVal = shellCp ( pSrc ); return retVal; } static int shellMkdir (wchar_t **pArgv) { int retVal = 0; /* assume valid return */ wchar_t *arg; wchar_t *pArg; wchar_t path[_MAX_PATH]; wchar_t tmpPath[_MAX_PATH]; wchar_t *pTmpPath = tmpPath; /* All the options are simply ignored in this implementation */ while ( *pArgv && **pArgv == '-' ) { if ( (*pArgv)[1] == 'm' ) { pArgv++; /* skip the next argument (mode) */ } pArgv++; } while ( *pArgv ) { arg = *pArgv; changeForwardSlashesToBackSlashes ( arg ); pArg = arg; pTmpPath = tmpPath; while ( 1 ) { /* create part of path */ while ( *pArg ) { *pTmpPath++ = *pArg++; if ( *pArg == '\\' ) break; } *pTmpPath = '\0'; /* check if directory already exists */ _wgetcwd ( path, _MAX_PATH ); if ( _wchdir ( tmpPath ) == -1 && _wmkdir ( tmpPath ) == -1 && // might have hit EEXIST _wchdir ( tmpPath ) == -1) { // so try again char buf[2048]; _snprintf(buf, 2048, "Could not create the directory: %S", tmpPath); perror ( buf ); retVal = 3; break; } else { // get back to the cwd _wchdir ( path ); } if ( *pArg == '\0' ) /* complete path? */ break; /* loop for next directory */ } pArgv++; } return retVal; } static const char * sh_GetLastErrorMessage() { static char buf[128]; FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* default language */ buf, sizeof(buf), NULL ); return buf; } /* * struct sh_FileData -- * * A pointer to the sh_FileData structure is passed into sh_RecordFileData, * which will fill in the fields. */ struct sh_FileData { wchar_t pathName[_MAX_PATH]; DWORD dwFileAttributes; }; /* * sh_RecordFileData -- * * Record the pathname and attributes of the file in * the sh_FileData structure pointed to by arg. * * Always return TRUE (successful completion). * * This function is intended to be passed into sh_EnumerateFiles * to see if a certain pattern expands to exactly one file/directory, * and if so, record its pathname and attributes. */ static BOOL sh_RecordFileData(wchar_t *pathName, WIN32_FIND_DATA *findData, void *arg) { struct sh_FileData *fData = (struct sh_FileData *) arg; wcscpy(fData->pathName, pathName); fData->dwFileAttributes = findData->dwFileAttributes; return TRUE; } static BOOL sh_DoCopy(wchar_t *srcFileName, DWORD srcFileAttributes, wchar_t *dstFileName, DWORD dstFileAttributes, int force, int recursive ) { if (dstFileAttributes != 0xFFFFFFFF) { if ((dstFileAttributes & FILE_ATTRIBUTE_READONLY) && force) { dstFileAttributes &= ~FILE_ATTRIBUTE_READONLY; SetFileAttributes(dstFileName, dstFileAttributes); } } if (srcFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { fprintf(stderr, "nsinstall: %ls is a directory\n", srcFileName); return FALSE; } else { DWORD r; wchar_t longSrc[1004] = LONGPATH_PREFIX; wchar_t longDst[1004] = LONGPATH_PREFIX; r = GetFullPathName(srcFileName, 1000, longSrc + STR_LEN(LONGPATH_PREFIX), NULL); if (!r) { fprintf(stderr, "nsinstall: couldn't get full path of %ls: %s\n", srcFileName, sh_GetLastErrorMessage()); return FALSE; } r = GetFullPathName(dstFileName, 1000, longDst + ARRAY_LEN(LONGPATH_PREFIX) - 1, NULL); if (!r) { fprintf(stderr, "nsinstall: couldn't get full path of %ls: %s\n", dstFileName, sh_GetLastErrorMessage()); return FALSE; } if (!CopyFile(longSrc, longDst, FALSE)) { fprintf(stderr, "nsinstall: cannot copy %ls to %ls: %s\n", srcFileName, dstFileName, sh_GetLastErrorMessage()); return FALSE; } } return TRUE; } /* * struct sh_CpCmdArg -- * * A pointer to the sh_CpCmdArg structure is passed into sh_CpFileCmd. * The sh_CpCmdArg contains information about the cp command, and * provide a buffer for constructing the destination file name. */ struct sh_CpCmdArg { int force; /* -f option, ok to overwrite an existing * read-only destination file */ int recursive; /* -r or -R option, recursively copy * directories. Note: this field is not used * by nsinstall and should always be 0. */ wchar_t *dstFileName; /* a buffer for constructing the destination * file name */ wchar_t *dstFileNameMarker; /* points to where in the dstFileName buffer * we should write the file component of the * destination file */ }; /* * sh_CpFileCmd -- * * Copy a file to the destination directory * * This function is intended to be passed into sh_EnumerateFiles to * copy all the files specified by the pattern to the destination * directory. * * Return TRUE if the file is successfully copied, and FALSE otherwise. */ static BOOL sh_CpFileCmd(wchar_t *pathName, WIN32_FIND_DATA *findData, void *cpArg) { BOOL retVal = TRUE; struct sh_CpCmdArg *arg = (struct sh_CpCmdArg *) cpArg; wcscpy(arg->dstFileNameMarker, findData->cFileName); return sh_DoCopy(pathName, findData->dwFileAttributes, arg->dstFileName, GetFileAttributes(arg->dstFileName), arg->force, arg->recursive); } static int shellCp (wchar_t **pArgv) { int retVal = 0; wchar_t **pSrc; wchar_t **pDst; struct sh_CpCmdArg arg; struct sh_FileData dstData; int dstIsDir = 0; int n; arg.force = 0; arg.recursive = 0; arg.dstFileName = dstData.pathName; arg.dstFileNameMarker = 0; while (*pArgv && **pArgv == '-') { wchar_t *p = *pArgv; while (*(++p)) { if (*p == 'f') { arg.force = 1; } } pArgv++; } /* the first source file */ if (*pArgv) { pSrc = pArgv++; } else { fprintf(stderr, "nsinstall: not enough arguments\n"); return 3; } /* get to the last token to find destination */ if (*pArgv) { pDst = pArgv++; } else { fprintf(stderr, "nsinstall: not enough arguments\n"); return 3; } while (*pArgv) { pDst = pArgv++; } /* * The destination pattern must unambiguously expand to exactly * one file or directory. */ changeForwardSlashesToBackSlashes(*pDst); sh_EnumerateFiles(*pDst, *pDst, sh_RecordFileData, &dstData, &n); assert(n >= 0); if (n == 1) { /* * Is the destination a file or directory? */ if (dstData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { dstIsDir = 1; } } else if (n > 1) { fprintf(stderr, "nsinstall: %ls: ambiguous destination file " "or directory\n", *pDst); return 3; } else { /* * n == 0, meaning that destination file or directory does * not exist. In this case the destination file directory * name must be fully specified. */ wchar_t *p; for (p = *pDst; *p; p++) { if (*p == '*' || *p == '?') { fprintf(stderr, "nsinstall: %ls: No such file or directory\n", *pDst); return 3; } } /* * Do not include the trailing \, if any, unless it is a root * directory (\ or X:\). */ if (p > *pDst && p[-1] == '\\' && p != *pDst + 1 && p[-2] != ':') { p[-1] = '\0'; } wcscpy(dstData.pathName, *pDst); dstData.dwFileAttributes = 0xFFFFFFFF; } /* * If there are two or more source files, the destination has * to be a directory. */ if (pDst - pSrc > 1 && !dstIsDir) { fprintf(stderr, "nsinstall: cannot copy more than" " one file to the same destination file\n"); return 3; } if (dstIsDir) { arg.dstFileNameMarker = arg.dstFileName + wcslen(arg.dstFileName); /* * Now arg.dstFileNameMarker is pointing to the null byte at the * end of string. We want to make sure that there is a \ at the * end of string, and arg.dstFileNameMarker should point right * after that \. */ if (arg.dstFileNameMarker[-1] != '\\') { *(arg.dstFileNameMarker++) = '\\'; } } if (!dstIsDir) { struct sh_FileData srcData; assert(pDst - pSrc == 1); changeForwardSlashesToBackSlashes(*pSrc); sh_EnumerateFiles(*pSrc, *pSrc, sh_RecordFileData, &srcData, &n); if (n == 0) { fprintf(stderr, "nsinstall: %ls: No such file or directory\n", *pSrc); retVal = 3; } else if (n > 1) { fprintf(stderr, "nsinstall: cannot copy more than one file or " "directory to the same destination\n"); retVal = 3; } else { assert(n == 1); if (sh_DoCopy(srcData.pathName, srcData.dwFileAttributes, dstData.pathName, dstData.dwFileAttributes, arg.force, arg.recursive) == FALSE) { retVal = 3; } } return retVal; } for ( ; *pSrc != *pDst; pSrc++) { BOOL rv; changeForwardSlashesToBackSlashes(*pSrc); rv = sh_EnumerateFiles(*pSrc, *pSrc, sh_CpFileCmd, &arg, &n); if (rv == FALSE) { retVal = 3; } else { if (n == 0) { fprintf(stderr, "nsinstall: %ls: No such file or directory\n", *pSrc); retVal = 3; } } } return retVal; } /* * sh_EnumerateFiles -- * * Enumerate all the files in the specified pattern, which is a pathname * containing possibly wildcard characters such as * and ?. fileFcn * is called on each file, passing the expanded file name, a pointer * to the file's WIN32_FILE_DATA, and the arg pointer. * * It is assumed that there are no wildcard characters before the * character pointed to by 'where'. * * On return, *nFiles stores the number of files enumerated. *nFiles is * set to this number whether sh_EnumerateFiles or 'fileFcn' succeeds * or not. * * Return TRUE if the files are successfully enumerated and all * 'fileFcn' invocations succeeded. Return FALSE if something went * wrong. */ static BOOL sh_EnumerateFiles( const wchar_t *pattern, const wchar_t *where, sh_FileFcn fileFcn, void *arg, int *nFiles ) { WIN32_FIND_DATA fileData; HANDLE hSearch; const wchar_t *src; wchar_t *dst; wchar_t fileName[_MAX_PATH]; wchar_t *fileNameMarker = fileName; wchar_t *oldFileNameMarker; BOOL hasWildcard = FALSE; BOOL retVal = TRUE; BOOL patternEndsInDotStar = FALSE; BOOL patternEndsInDot = FALSE; /* a special case of * patternEndsInDotStar */ int numDotsInPattern; int len; /* * Windows expands patterns ending in ".", ".*", ".**", etc. * differently from the glob expansion on Unix. For example, * both "foo." and "foo.*" match "foo", and "*.*" matches * everything, including filenames with no dots. So we need * to throw away extra files returned by the FindNextFile() * function. We require that a matched filename have at least * the number of dots in the pattern. */ len = wcslen(pattern); if (len >= 2) { /* Start from the end of pattern and go backward */ const wchar_t *p = &pattern[len - 1]; /* We can have zero or more *'s */ while (p >= pattern && *p == '*') { p--; } if (p >= pattern && *p == '.') { patternEndsInDotStar = TRUE; if (p == &pattern[len - 1]) { patternEndsInDot = TRUE; } p--; numDotsInPattern = 1; while (p >= pattern && *p != '\\') { if (*p == '.') { numDotsInPattern++; } p--; } } } *nFiles = 0; /* * Copy pattern to fileName, but only up to and not including * the first \ after the first wildcard letter. * * Make fileNameMarker point to one of the following: * - the start of fileName, if fileName does not contain any \. * - right after the \ before the first wildcard letter, if there is * a wildcard character. * - right after the last \, if there is no wildcard character. */ dst = fileName; src = pattern; while (src < where) { if (*src == '\\') { oldFileNameMarker = fileNameMarker; fileNameMarker = dst + 1; } *(dst++) = *(src++); } while (*src && *src != '*' && *src != '?') { if (*src == '\\') { oldFileNameMarker = fileNameMarker; fileNameMarker = dst + 1; } *(dst++) = *(src++); } if (*src) { /* * Must have seen the first wildcard letter */ hasWildcard = TRUE; while (*src && *src != '\\') { *(dst++) = *(src++); } } /* Now src points to either null or \ */ assert(*src == '\0' || *src == '\\'); assert(hasWildcard || *src == '\0'); *dst = '\0'; /* * If the pattern does not contain any wildcard characters, then * we don't need to go the FindFirstFile route. */ if (!hasWildcard) { /* * See if it is the root directory, \, or X:\. */ assert(!wcscmp(fileName, pattern)); assert(wcslen(fileName) >= 1); if (dst[-1] == '\\' && (dst == fileName + 1 || dst[-2] == ':')) { fileData.cFileName[0] = '\0'; } else { /* * Do not include the trailing \, if any */ if (dst[-1] == '\\') { assert(*fileNameMarker == '\0'); dst[-1] = '\0'; fileNameMarker = oldFileNameMarker; } wcscpy(fileData.cFileName, fileNameMarker); } fileData.dwFileAttributes = GetFileAttributes(fileName); if (fileData.dwFileAttributes == 0xFFFFFFFF) { return TRUE; } *nFiles = 1; return (*fileFcn)(fileName, &fileData, arg); } hSearch = FindFirstFile(fileName, &fileData); if (hSearch == INVALID_HANDLE_VALUE) { return retVal; } do { if (!wcscmp(fileData.cFileName, L".") || !wcscmp(fileData.cFileName, L"..")) { /* * Skip over . and .. */ continue; } if (patternEndsInDotStar) { int nDots = 0; wchar_t *p = fileData.cFileName; while (*p) { if (*p == '.') { nDots++; } p++; } /* Now p points to the null byte at the end of file name */ if (patternEndsInDot && (p == fileData.cFileName || p[-1] != '.')) { /* * File name does not end in dot. Skip this file. * Note: windows file name probably cannot end in dot, * but we do this check anyway. */ continue; } if (nDots < numDotsInPattern) { /* * Not enough dots in file name. Must be an extra * file in matching .* pattern. Skip this file. */ continue; } } wcscpy(fileNameMarker, fileData.cFileName); if (*src && *(src + 1)) { /* * More to go. Recurse. */ int n; assert(*src == '\\'); where = fileName + wcslen(fileName); wcscat(fileName, src); sh_EnumerateFiles(fileName, where, fileFcn, arg, &n); *nFiles += n; } else { assert(wcschr(fileName, '*') == NULL); assert(wcschr(fileName, '?') == NULL); (*nFiles)++; if ((*fileFcn)(fileName, &fileData, arg) == FALSE) { retVal = FALSE; } } } while (FindNextFile(hSearch, &fileData)); FindClose(hSearch); return retVal; }