2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
37 extern int defaultColor;
38 extern BOOL echo_mode;
39 extern BOOL interactive;
41 struct env_stack *pushd_directories;
42 const WCHAR dotW[] = {'.','\0'};
43 const WCHAR dotdotW[] = {'.','.','\0'};
44 const WCHAR nullW[] = {'\0'};
45 const WCHAR starW[] = {'*','\0'};
46 const WCHAR slashW[] = {'\\','\0'};
47 const WCHAR equalW[] = {'=','\0'};
48 const WCHAR inbuilt[][10] = {
49 {'C','A','L','L','\0'},
51 {'C','H','D','I','R','\0'},
53 {'C','O','P','Y','\0'},
54 {'C','T','T','Y','\0'},
55 {'D','A','T','E','\0'},
58 {'E','C','H','O','\0'},
59 {'E','R','A','S','E','\0'},
61 {'G','O','T','O','\0'},
62 {'H','E','L','P','\0'},
64 {'L','A','B','E','L','\0'},
66 {'M','K','D','I','R','\0'},
67 {'M','O','V','E','\0'},
68 {'P','A','T','H','\0'},
69 {'P','A','U','S','E','\0'},
70 {'P','R','O','M','P','T','\0'},
73 {'R','E','N','A','M','E','\0'},
75 {'R','M','D','I','R','\0'},
77 {'S','H','I','F','T','\0'},
78 {'S','T','A','R','T','\0'},
79 {'T','I','M','E','\0'},
80 {'T','I','T','L','E','\0'},
81 {'T','Y','P','E','\0'},
82 {'V','E','R','I','F','Y','\0'},
85 {'E','N','D','L','O','C','A','L','\0'},
86 {'S','E','T','L','O','C','A','L','\0'},
87 {'P','U','S','H','D','\0'},
88 {'P','O','P','D','\0'},
89 {'A','S','S','O','C','\0'},
90 {'C','O','L','O','R','\0'},
91 {'F','T','Y','P','E','\0'},
92 {'M','O','R','E','\0'},
93 {'C','H','O','I','C','E','\0'},
94 {'E','X','I','T','\0'}
96 static const WCHAR externals[][10] = {
97 {'A','T','T','R','I','B','\0'},
98 {'X','C','O','P','Y','\0'}
100 static const WCHAR fslashW[] = {'/','\0'};
101 static const WCHAR onW[] = {'O','N','\0'};
102 static const WCHAR offW[] = {'O','F','F','\0'};
103 static const WCHAR parmY[] = {'/','Y','\0'};
104 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
106 static HINSTANCE hinst;
107 struct env_stack *saved_environment;
108 static BOOL verify_mode = FALSE;
110 /**************************************************************************
113 * Issue a message and ask for confirmation, waiting on a valid answer.
115 * Returns True if Y (or A) answer is selected
116 * If optionAll contains a pointer, ALL is allowed, and if answered
120 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
124 WCHAR confirm[MAXSTRING];
125 WCHAR options[MAXSTRING];
126 WCHAR Ybuffer[MAXSTRING];
127 WCHAR Nbuffer[MAXSTRING];
128 WCHAR Abuffer[MAXSTRING];
129 WCHAR answer[MAX_PATH] = {'\0'};
132 /* Load the translated valid answers */
134 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
135 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
136 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
137 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
138 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
139 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
141 /* Loop waiting on a valid answer */
146 WCMD_output_asis (message);
148 WCMD_output_asis (confirm);
149 WCMD_output_asis (options);
150 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
151 answer[0] = toupperW(answer[0]);
152 if (answer[0] == Ybuffer[0])
154 if (answer[0] == Nbuffer[0])
156 if (optionAll && answer[0] == Abuffer[0])
164 /****************************************************************************
167 * Clear the terminal screen.
170 void WCMD_clear_screen (void) {
172 /* Emulate by filling the screen from the top left to bottom right with
173 spaces, then moving the cursor to the top left afterwards */
174 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
175 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
177 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
182 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
186 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
187 SetConsoleCursorPosition(hStdOut, topLeft);
191 /****************************************************************************
194 * Change the default i/o device (ie redirect STDin/STDout).
197 void WCMD_change_tty (void) {
199 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
203 /****************************************************************************
208 void WCMD_choice (const WCHAR * command) {
210 static const WCHAR bellW[] = {7,0};
211 static const WCHAR commaW[] = {',',0};
212 static const WCHAR bracket_open[] = {'[',0};
213 static const WCHAR bracket_close[] = {']','?',0};
218 WCHAR *my_command = NULL;
219 WCHAR opt_default = 0;
220 DWORD opt_timeout = 0;
227 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
230 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
234 ptr = WCMD_skip_leading_spaces(my_command);
235 while (*ptr == '/') {
236 switch (toupperW(ptr[1])) {
239 /* the colon is optional */
243 if (!*ptr || isspaceW(*ptr)) {
244 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
245 HeapFree(GetProcessHeap(), 0, my_command);
249 /* remember the allowed keys (overwrite previous /C option) */
251 while (*ptr && (!isspaceW(*ptr)))
255 /* terminate allowed chars */
257 ptr = WCMD_skip_leading_spaces(&ptr[1]);
259 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
264 ptr = WCMD_skip_leading_spaces(&ptr[2]);
269 ptr = WCMD_skip_leading_spaces(&ptr[2]);
274 /* the colon is optional */
278 opt_default = *ptr++;
280 if (!opt_default || (*ptr != ',')) {
281 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
282 HeapFree(GetProcessHeap(), 0, my_command);
288 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
294 opt_timeout = atoiW(answer);
296 ptr = WCMD_skip_leading_spaces(ptr);
300 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
301 HeapFree(GetProcessHeap(), 0, my_command);
307 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
310 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
312 /* use default keys, when needed: localized versions of "Y"es and "No" */
314 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
315 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
320 /* print the question, when needed */
322 WCMD_output_asis(ptr);
326 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
330 /* print a list of all allowed answers inside brackets */
331 WCMD_output_asis(bracket_open);
334 while ((answer[0] = *ptr++)) {
335 WCMD_output_asis(answer);
337 WCMD_output_asis(commaW);
339 WCMD_output_asis(bracket_close);
344 /* FIXME: Add support for option /T */
345 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
348 answer[0] = toupperW(answer[0]);
350 ptr = strchrW(opt_c, answer[0]);
352 WCMD_output_asis(answer);
353 WCMD_output_asis(newlineW);
355 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
357 errorlevel = (ptr - opt_c) + 1;
358 WINE_TRACE("answer: %d\n", errorlevel);
359 HeapFree(GetProcessHeap(), 0, my_command);
364 /* key not allowed: play the bell */
365 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
366 WCMD_output_asis(bellW);
371 /****************************************************************************
374 * Copy a file or wildcarded set.
375 * FIXME: Add support for a+b+c type syntax
378 void WCMD_copy (void) {
383 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
385 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
386 BOOL copyToDir = FALSE;
387 WCHAR srcspec[MAX_PATH];
391 WCHAR fname[MAX_PATH];
394 if (param1[0] == 0x00) {
395 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
399 /* Convert source into full spec */
400 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
401 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
402 if (srcpath[strlenW(srcpath) - 1] == '\\')
403 srcpath[strlenW(srcpath) - 1] = '\0';
405 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
406 attribs = GetFileAttributesW(srcpath);
410 strcpyW(srcspec, srcpath);
412 /* If a directory, then add \* on the end when searching */
413 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
414 strcatW(srcpath, slashW);
415 strcatW(srcspec, slashW);
416 strcatW(srcspec, starW);
418 WCMD_splitpath(srcpath, drive, dir, fname, ext);
419 strcpyW(srcpath, drive);
420 strcatW(srcpath, dir);
423 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
425 /* If no destination supplied, assume current directory */
426 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
427 if (param2[0] == 0x00) {
428 strcpyW(param2, dotW);
431 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
432 if (outpath[strlenW(outpath) - 1] == '\\')
433 outpath[strlenW(outpath) - 1] = '\0';
434 attribs = GetFileAttributesW(outpath);
435 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
436 strcatW (outpath, slashW);
439 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
440 wine_dbgstr_w(outpath), copyToDir);
442 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
443 if (strstrW (quals, parmNoY))
445 else if (strstrW (quals, parmY))
448 /* By default, we will force the overwrite in batch mode and ask for
449 * confirmation in interactive mode. */
450 force = !interactive;
452 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
453 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
454 * default behavior. */
455 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
456 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
457 if (!lstrcmpiW (copycmd, parmY))
459 else if (!lstrcmpiW (copycmd, parmNoY))
464 /* Loop through all source files */
465 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
466 hff = FindFirstFileW(srcspec, &fd);
467 if (hff != INVALID_HANDLE_VALUE) {
469 WCHAR outname[MAX_PATH];
470 WCHAR srcname[MAX_PATH];
471 BOOL overwrite = force;
473 /* Destination is either supplied filename, or source name in
474 supplied destination directory */
475 strcpyW(outname, outpath);
476 if (copyToDir) strcatW(outname, fd.cFileName);
477 strcpyW(srcname, srcpath);
478 strcatW(srcname, fd.cFileName);
480 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
481 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
483 /* Skip . and .., and directories */
484 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
486 WINE_TRACE("Skipping directories\n");
489 /* Prompt before overwriting */
490 else if (!overwrite) {
491 attribs = GetFileAttributesW(outname);
492 if (attribs != INVALID_FILE_ATTRIBUTES) {
494 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
495 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
498 else overwrite = TRUE;
501 /* Do the copy as appropriate */
503 status = CopyFileW(srcname, outname, FALSE);
504 if (!status) WCMD_print_error ();
507 } while (FindNextFileW(hff, &fd) != 0);
514 /****************************************************************************
517 * Create a directory (and, if needed, any intermediate directories).
519 * Modifies its argument by replacing slashes temporarily with nulls.
522 static BOOL create_full_path(WCHAR* path)
526 /* don't mess with drive letter portion of path, if any */
531 /* Strip trailing slashes. */
532 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
535 /* Step through path, creating intermediate directories as needed. */
536 /* First component includes drive letter, if any. */
540 /* Skip to end of component */
541 while (*p == '\\') p++;
542 while (*p && *p != '\\') p++;
544 /* path is now the original full path */
545 return CreateDirectoryW(path, NULL);
547 /* Truncate path, create intermediate directory, and restore path */
549 rv = CreateDirectoryW(path, NULL);
551 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
558 void WCMD_create_dir (WCHAR *command) {
560 WCHAR *argN = command;
562 if (param1[0] == 0x00) {
563 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
566 /* Loop through all args */
568 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL, FALSE);
570 if (!create_full_path(thisArg)) {
577 /* Parse the /A options given by the user on the commandline
578 * into a bitmask of wanted attributes (*wantSet),
579 * and a bitmask of unwanted attributes (*wantClear).
581 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
582 static const WCHAR parmA[] = {'/','A','\0'};
585 /* both are strictly 'out' parameters */
589 /* For each /A argument */
590 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
594 /* Skip optional : */
597 /* For each of the attribute specifier chars to this /A option */
598 for (; *p != 0 && *p != '/'; p++) {
607 /* Convert the attribute specifier to a bit in one of the masks */
609 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
610 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
611 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
612 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
614 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
624 /* If filename part of parameter is * or *.*,
625 * and neither /Q nor /P options were given,
626 * prompt the user whether to proceed.
627 * Returns FALSE if user says no, TRUE otherwise.
628 * *pPrompted is set to TRUE if the user is prompted.
629 * (If /P supplied, del will prompt for individual files later.)
631 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
632 static const WCHAR parmP[] = {'/','P','\0'};
633 static const WCHAR parmQ[] = {'/','Q','\0'};
635 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
636 static const WCHAR anyExt[]= {'.','*','\0'};
639 WCHAR fname[MAX_PATH];
641 WCHAR fpath[MAX_PATH];
643 /* Convert path into actual directory spec */
644 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
645 WCMD_splitpath(fpath, drive, dir, fname, ext);
647 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
648 if ((strcmpW(fname, starW) == 0) &&
649 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
651 WCHAR question[MAXSTRING];
652 static const WCHAR fmt[] = {'%','s',' ','\0'};
654 /* Caller uses this to suppress "file not found" warning later */
657 /* Ask for confirmation */
658 wsprintfW(question, fmt, fpath);
659 return WCMD_ask_confirm(question, TRUE, NULL);
662 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
666 /* Helper function for WCMD_delete().
667 * Deletes a single file, directory, or wildcard.
668 * If /S was given, does it recursively.
669 * Returns TRUE if a file was deleted.
671 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
673 static const WCHAR parmP[] = {'/','P','\0'};
674 static const WCHAR parmS[] = {'/','S','\0'};
675 static const WCHAR parmF[] = {'/','F','\0'};
677 DWORD unwanted_attrs;
679 WCHAR argCopy[MAX_PATH];
682 WCHAR fpath[MAX_PATH];
684 BOOL handleParm = TRUE;
686 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
688 strcpyW(argCopy, thisArg);
689 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
690 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
692 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
693 /* Skip this arg if user declines to delete *.* */
697 /* First, try to delete in the current directory */
698 hff = FindFirstFileW(argCopy, &fd);
699 if (hff == INVALID_HANDLE_VALUE) {
705 /* Support del <dirname> by just deleting all files dirname\* */
707 && (strchrW(argCopy,'*') == NULL)
708 && (strchrW(argCopy,'?') == NULL)
709 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
711 WCHAR modifiedParm[MAX_PATH];
712 static const WCHAR slashStar[] = {'\\','*','\0'};
714 strcpyW(modifiedParm, argCopy);
715 strcatW(modifiedParm, slashStar);
718 WCMD_delete_one(modifiedParm);
720 } else if (handleParm) {
722 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
723 strcpyW (fpath, argCopy);
725 p = strrchrW (fpath, '\\');
728 strcatW (fpath, fd.cFileName);
730 else strcpyW (fpath, fd.cFileName);
731 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
734 /* Handle attribute matching (/A) */
735 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
736 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
738 /* /P means prompt for each file */
739 if (ok && strstrW (quals, parmP) != NULL) {
742 /* Ask for confirmation */
743 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
744 ok = WCMD_ask_confirm(question, FALSE, NULL);
748 /* Only proceed if ok to */
751 /* If file is read only, and /A:r or /F supplied, delete it */
752 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
753 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
754 strstrW (quals, parmF) != NULL)) {
755 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
758 /* Now do the delete */
759 if (!DeleteFileW(fpath)) WCMD_print_error ();
763 } while (FindNextFileW(hff, &fd) != 0);
767 /* Now recurse into all subdirectories handling the parameter in the same way */
768 if (strstrW (quals, parmS) != NULL) {
770 WCHAR thisDir[MAX_PATH];
775 WCHAR fname[MAX_PATH];
778 /* Convert path into actual directory spec */
779 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
780 WCMD_splitpath(thisDir, drive, dir, fname, ext);
782 strcpyW(thisDir, drive);
783 strcatW(thisDir, dir);
784 cPos = strlenW(thisDir);
786 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
788 /* Append '*' to the directory */
790 thisDir[cPos+1] = 0x00;
792 hff = FindFirstFileW(thisDir, &fd);
794 /* Remove residual '*' */
795 thisDir[cPos] = 0x00;
797 if (hff != INVALID_HANDLE_VALUE) {
798 DIRECTORY_STACK *allDirs = NULL;
799 DIRECTORY_STACK *lastEntry = NULL;
802 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
803 (strcmpW(fd.cFileName, dotdotW) != 0) &&
804 (strcmpW(fd.cFileName, dotW) != 0)) {
806 DIRECTORY_STACK *nextDir;
807 WCHAR subParm[MAX_PATH];
809 /* Work out search parameter in sub dir */
810 strcpyW (subParm, thisDir);
811 strcatW (subParm, fd.cFileName);
812 strcatW (subParm, slashW);
813 strcatW (subParm, fname);
814 strcatW (subParm, ext);
815 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
817 /* Allocate memory, add to list */
818 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
819 if (allDirs == NULL) allDirs = nextDir;
820 if (lastEntry != NULL) lastEntry->next = nextDir;
822 nextDir->next = NULL;
823 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
824 (strlenW(subParm)+1) * sizeof(WCHAR));
825 strcpyW(nextDir->dirName, subParm);
827 } while (FindNextFileW(hff, &fd) != 0);
830 /* Go through each subdir doing the delete */
831 while (allDirs != NULL) {
832 DIRECTORY_STACK *tempDir;
834 tempDir = allDirs->next;
835 found |= WCMD_delete_one (allDirs->dirName);
837 HeapFree(GetProcessHeap(),0,allDirs->dirName);
838 HeapFree(GetProcessHeap(),0,allDirs);
847 /****************************************************************************
850 * Delete a file or wildcarded set.
853 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
854 * - Each set is a pattern, eg /ahr /as-r means
855 * readonly+hidden OR nonreadonly system files
856 * - The '-' applies to a single field, ie /a:-hr means read only
860 BOOL WCMD_delete (WCHAR *command) {
863 BOOL argsProcessed = FALSE;
864 BOOL foundAny = FALSE;
868 for (argno=0; ; argno++) {
873 thisArg = WCMD_parameter (command, argno, &argN, NULL, FALSE);
875 break; /* no more parameters */
877 continue; /* skip options */
879 argsProcessed = TRUE;
880 found = WCMD_delete_one(thisArg);
883 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
888 /* Handle no valid args */
890 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
898 * Returns a trimmed version of s with all leading and trailing whitespace removed
902 static WCHAR *WCMD_strtrim(const WCHAR *s)
904 DWORD len = strlenW(s);
905 const WCHAR *start = s;
908 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
911 while (isspaceW(*start)) start++;
913 const WCHAR *end = s + len - 1;
914 while (end > start && isspaceW(*end)) end--;
915 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
916 result[end - start + 1] = '\0';
924 /****************************************************************************
927 * Echo input to the screen (or not). We don't try to emulate the bugs
928 * in DOS (try typing "ECHO ON AGAIN" for an example).
931 void WCMD_echo (const WCHAR *command)
934 const WCHAR *origcommand = command;
937 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
938 || command[0]==':' || command[0]==';')
941 trimmed = WCMD_strtrim(command);
942 if (!trimmed) return;
944 count = strlenW(trimmed);
945 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
946 && origcommand[0]!=';') {
947 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
948 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
952 if (lstrcmpiW(trimmed, onW) == 0)
954 else if (lstrcmpiW(trimmed, offW) == 0)
957 WCMD_output_asis (command);
958 WCMD_output_asis (newlineW);
960 HeapFree(GetProcessHeap(), 0, trimmed);
963 /*****************************************************************************
966 * Execute a command, and any && or bracketed follow on to the command. The
967 * first command to be executed may not be at the front of the
968 * commands->thiscommand string (eg. it may point after a DO or ELSE)
970 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
971 const WCHAR *variable, const WCHAR *value,
972 BOOL isIF, BOOL executecmds)
974 CMD_LIST *curPosition = *cmdList;
975 int myDepth = (*cmdList)->bracketDepth;
977 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
978 cmdList, wine_dbgstr_w(firstcmd),
979 wine_dbgstr_w(variable), wine_dbgstr_w(value),
982 /* Skip leading whitespace between condition and the command */
983 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
985 /* Process the first command, if there is one */
986 if (executecmds && firstcmd && *firstcmd) {
987 WCHAR *command = WCMD_strdupW(firstcmd);
988 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
989 HeapFree(GetProcessHeap(), 0, command);
993 /* If it didn't move the position, step to next command */
994 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
996 /* Process any other parts of the command */
998 BOOL processThese = executecmds;
1001 static const WCHAR ifElse[] = {'e','l','s','e'};
1003 /* execute all appropriate commands */
1004 curPosition = *cmdList;
1006 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1008 (*cmdList)->prevDelim,
1009 (*cmdList)->bracketDepth, myDepth);
1011 /* Execute any statements appended to the line */
1012 /* FIXME: Only if previous call worked for && or failed for || */
1013 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1014 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1015 if (processThese && (*cmdList)->command) {
1016 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1019 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1021 /* Execute any appended to the statement with (...) */
1022 } else if ((*cmdList)->bracketDepth > myDepth) {
1024 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1025 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1027 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1029 /* End of the command - does 'ELSE ' follow as the next command? */
1032 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1033 (*cmdList)->command)) {
1035 /* Swap between if and else processing */
1036 processThese = !processThese;
1038 /* Process the ELSE part */
1040 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1041 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1043 /* Skip leading whitespace between condition and the command */
1044 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1046 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1049 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1051 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1060 /**************************************************************************
1063 * Batch file loop processing.
1065 * On entry: cmdList contains the syntax up to the set
1066 * next cmdList and all in that bracket contain the set data
1067 * next cmdlist contains the DO cmd
1068 * following that is either brackets or && entries (as per if)
1072 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1074 WIN32_FIND_DATAW fd;
1077 static const WCHAR inW[] = {'i','n'};
1078 static const WCHAR doW[] = {'d','o'};
1079 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1083 WCHAR optionsRoot[MAX_PATH];
1084 DIRECTORY_STACK *dirsToWalk = NULL;
1086 BOOL expandDirs = FALSE;
1087 BOOL useNumbers = FALSE;
1088 BOOL doFileset = FALSE;
1089 BOOL doRecurse = FALSE;
1090 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
1091 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1093 CMD_LIST *thisCmdStart;
1094 int parameterNo = 0;
1096 /* Handle optional qualifiers (multiple are allowed) */
1097 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1100 while (thisArg && *thisArg == '/') {
1101 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
1103 switch (toupperW(*thisArg)) {
1104 case 'D': expandDirs = TRUE; break;
1105 case 'L': useNumbers = TRUE; break;
1107 /* Recursive is special case - /R can have an optional path following it */
1108 /* filenamesets are another special case - /F can have an optional options following it */
1112 /* When recursing directories, use current directory as the starting point unless
1113 subsequently overridden */
1114 doRecurse = (toupperW(*thisArg) == 'R');
1115 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
1117 doFileset = (toupperW(*thisArg) == 'F');
1119 /* Retrieve next parameter to see if is root/options (raw form required
1120 with for /f, or unquoted in for /r) */
1121 thisArg = WCMD_parameter(p, parameterNo, NULL, NULL, doFileset);
1123 /* Next parm is either qualifier, path/options or variable -
1124 only care about it if it is the path/options */
1125 if (thisArg && *thisArg != '/' && *thisArg != '%') {
1127 strcpyW(optionsRoot, thisArg);
1129 static unsigned int once;
1130 if (!once++) WINE_FIXME("/F needs to handle options\n");
1136 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
1139 /* Step to next token */
1140 thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1143 /* Ensure line continues with variable */
1144 if (!*thisArg || *thisArg != '%') {
1145 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1149 /* Set up the list of directories to recurse if we are going to */
1151 /* Allocate memory, add to list */
1152 dirsToWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1153 dirsToWalk->next = NULL;
1154 dirsToWalk->dirName = HeapAlloc(GetProcessHeap(),0,
1155 (strlenW(optionsRoot) + 1) * sizeof(WCHAR));
1156 strcpyW(dirsToWalk->dirName, optionsRoot);
1157 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
1160 /* Variable should follow */
1161 strcpyW(variable, thisArg);
1162 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1164 /* Ensure line continues with IN */
1165 thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1167 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1168 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
1169 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
1170 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1174 /* Save away where the set of data starts and the variable */
1175 thisDepth = (*cmdList)->bracketDepth;
1176 *cmdList = (*cmdList)->nextcommand;
1177 setStart = (*cmdList);
1179 /* Skip until the close bracket */
1180 WINE_TRACE("Searching %p as the set\n", *cmdList);
1182 (*cmdList)->command != NULL &&
1183 (*cmdList)->bracketDepth > thisDepth) {
1184 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1185 *cmdList = (*cmdList)->nextcommand;
1188 /* Skip the close bracket, if there is one */
1189 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1191 /* Syntax error if missing close bracket, or nothing following it
1192 and once we have the complete set, we expect a DO */
1193 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1194 if ((*cmdList == NULL)
1195 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1197 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1203 /* Loop repeatedly per-directory we are potentially walking, when in for /r
1204 mode, or once for the rest of the time. */
1206 WCHAR fullitem[MAX_PATH];
1207 static const WCHAR slashstarW[] = {'\\','*','\0'};
1209 /* Save away the starting position for the commands (and offset for the
1211 cmdStart = *cmdList;
1212 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1215 /* If we are recursing directories (ie /R), add all sub directories now, then
1216 prefix the root when searching for the item */
1218 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1220 /* Build a generic search and add all directories on the list of directories
1222 strcpyW(fullitem, dirsToWalk->dirName);
1223 strcatW(fullitem, slashstarW);
1224 hff = FindFirstFileW(fullitem, &fd);
1225 if (hff != INVALID_HANDLE_VALUE) {
1227 WINE_TRACE("Looking for subdirectories\n");
1228 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1229 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1230 (strcmpW(fd.cFileName, dotW) != 0))
1232 /* Allocate memory, add to list */
1233 DIRECTORY_STACK *toWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1234 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1235 toWalk->next = remainingDirs->next;
1236 remainingDirs->next = toWalk;
1237 remainingDirs = toWalk;
1238 toWalk->dirName = HeapAlloc(GetProcessHeap(), 0,
1240 (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1241 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1242 strcatW(toWalk->dirName, slashW);
1243 strcatW(toWalk->dirName, fd.cFileName);
1244 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1245 toWalk, toWalk->next);
1247 } while (FindNextFileW(hff, &fd) != 0);
1248 WINE_TRACE("Finished adding all subdirectories\n");
1254 /* Loop through all set entries */
1256 thisSet->command != NULL &&
1257 thisSet->bracketDepth >= thisDepth) {
1259 /* Loop through all entries on the same line */
1263 WINE_TRACE("Processing for set %p\n", thisSet);
1265 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL, TRUE))) {
1268 * If the parameter within the set has a wildcard then search for matching files
1269 * otherwise do a literal substitution.
1271 static const WCHAR wildcards[] = {'*','?','\0'};
1272 thisCmdStart = cmdStart;
1275 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1277 if (!useNumbers && !doFileset) {
1278 WCHAR fullitem[MAX_PATH];
1280 /* Now build the item to use / search for in the specified directory,
1281 as it is fully qualified in the /R case */
1283 strcpyW(fullitem, dirsToWalk->dirName);
1284 strcatW(fullitem, slashW);
1285 strcatW(fullitem, item);
1287 strcpyW(fullitem, item);
1290 if (strpbrkW (fullitem, wildcards)) {
1292 hff = FindFirstFileW(fullitem, &fd);
1293 if (hff != INVALID_HANDLE_VALUE) {
1295 BOOL isDirectory = FALSE;
1297 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1299 /* Handle as files or dirs appropriately, but ignore . and .. */
1300 if (isDirectory == expandDirs &&
1301 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1302 (strcmpW(fd.cFileName, dotW) != 0))
1304 thisCmdStart = cmdStart;
1305 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1308 strcpyW(fullitem, dirsToWalk->dirName);
1309 strcatW(fullitem, slashW);
1310 strcatW(fullitem, fd.cFileName);
1312 strcpyW(fullitem, fd.cFileName);
1315 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1316 fullitem, FALSE, TRUE);
1317 cmdEnd = thisCmdStart;
1319 } while (FindNextFileW(hff, &fd) != 0);
1324 WCMD_part_execute(&thisCmdStart, firstCmd, variable, fullitem, FALSE, TRUE);
1325 cmdEnd = thisCmdStart;
1328 } else if (useNumbers) {
1329 /* Convert the first 3 numbers to signed longs and save */
1330 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1331 /* else ignore them! */
1333 /* Filesets - either a list of files, or a command to run and parse the output */
1334 } else if (doFileset && *itemStart != '"') {
1337 WCHAR temp_file[MAX_PATH];
1339 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1340 wine_dbgstr_w(item));
1342 /* If backquote or single quote, we need to launch that command
1343 and parse the results - use a temporary file */
1344 if (*itemStart == '`' || *itemStart == '\'') {
1346 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1347 static const WCHAR redirOut[] = {'>','%','s','\0'};
1348 static const WCHAR cmdW[] = {'C','M','D','\0'};
1350 /* Remove trailing character */
1351 itemStart[strlenW(itemStart)-1] = 0x00;
1353 /* Get temp filename */
1354 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1355 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1357 /* Execute program and redirect output */
1358 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1359 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1361 /* Open the file, read line by line and process */
1362 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1363 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1366 /* Open the file, read line by line and process */
1367 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1368 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1371 /* Process the input file */
1372 if (input == INVALID_HANDLE_VALUE) {
1373 WCMD_print_error ();
1374 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1376 return; /* FOR loop aborts at first failure here */
1380 WCHAR buffer[MAXSTRING];
1381 WCHAR *where, *parm;
1383 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1385 /* Skip blank lines*/
1386 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
1387 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1388 wine_dbgstr_w(buffer));
1391 /* FIXME: The following should be moved into its own routine and
1392 reused for the string literal parsing below */
1393 thisCmdStart = cmdStart;
1395 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1396 cmdEnd = thisCmdStart;
1402 CloseHandle (input);
1405 /* Delete the temporary file */
1406 if (*itemStart == '`' || *itemStart == '\'') {
1407 DeleteFileW(temp_file);
1410 /* Filesets - A string literal */
1411 } else if (doFileset && *itemStart == '"') {
1412 WCHAR buffer[MAXSTRING];
1413 WCHAR *where, *parm;
1415 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1416 strcpyW(buffer, item);
1417 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
1418 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1419 wine_dbgstr_w(buffer));
1422 /* FIXME: The following should be moved into its own routine and
1423 reused for the string literal parsing below */
1424 thisCmdStart = cmdStart;
1426 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1427 cmdEnd = thisCmdStart;
1431 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1435 /* Move onto the next set line */
1436 thisSet = thisSet->nextcommand;
1439 /* If /L is provided, now run the for loop */
1442 static const WCHAR fmt[] = {'%','d','\0'};
1444 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1445 numbers[0], numbers[2], numbers[1]);
1447 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
1450 sprintfW(thisNum, fmt, i);
1451 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1453 thisCmdStart = cmdStart;
1455 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1457 cmdEnd = thisCmdStart;
1460 /* If we are walking directories, move on to any which remain */
1461 if (dirsToWalk != NULL) {
1462 DIRECTORY_STACK *nextDir = dirsToWalk->next;
1463 HeapFree(GetProcessHeap(), 0, dirsToWalk->dirName);
1464 HeapFree(GetProcessHeap(), 0, dirsToWalk);
1465 dirsToWalk = nextDir;
1466 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
1467 wine_dbgstr_w(dirsToWalk->dirName));
1468 else WINE_TRACE("Finished all directories.\n");
1471 } while (dirsToWalk != NULL);
1473 /* Now skip over the do part if we did not perform the for loop so far.
1474 We store in cmdEnd the next command after the do block, but we only
1475 know this if something was run. If it has not been, we need to calculate
1478 thisCmdStart = cmdStart;
1479 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
1480 WCMD_part_execute(&thisCmdStart, firstCmd, NULL, NULL, FALSE, FALSE);
1481 cmdEnd = thisCmdStart;
1484 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1485 all processing, OR it should be pointing to the end of && processing OR
1486 it should be pointing at the NULL end of bracket for the DO. The return
1487 value needs to be the NEXT command to execute, which it either is, or
1488 we need to step over the closing bracket */
1490 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1493 /**************************************************************************
1496 * Simple on-line help. Help text is stored in the resource file.
1499 void WCMD_give_help (const WCHAR *command)
1503 command = WCMD_skip_leading_spaces((WCHAR*) command);
1504 if (strlenW(command) == 0) {
1505 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1508 /* Display help message for builtin commands */
1509 for (i=0; i<=WCMD_EXIT; i++) {
1510 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1511 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1512 WCMD_output_asis (WCMD_LoadMessage(i));
1516 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1517 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1518 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1519 command, -1, externals[i], -1) == CSTR_EQUAL) {
1521 static const WCHAR helpW[] = {' ', '/','?','\0'};
1522 strcpyW(cmd, command);
1523 strcatW(cmd, helpW);
1524 WCMD_run_program(cmd, FALSE);
1528 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1533 /****************************************************************************
1536 * Batch file jump instruction. Not the most efficient algorithm ;-)
1537 * Prints error message if the specified label cannot be found - the file pointer is
1538 * then at EOF, effectively stopping the batch file.
1539 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1542 void WCMD_goto (CMD_LIST **cmdList) {
1544 WCHAR string[MAX_PATH];
1545 WCHAR current[MAX_PATH];
1547 /* Do not process any more parts of a processed multipart or multilines command */
1548 if (cmdList) *cmdList = NULL;
1550 if (context != NULL) {
1551 WCHAR *paramStart = param1, *str;
1552 static const WCHAR eofW[] = {':','e','o','f','\0'};
1554 if (param1[0] == 0x00) {
1555 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1559 /* Handle special :EOF label */
1560 if (lstrcmpiW (eofW, param1) == 0) {
1561 context -> skip_rest = TRUE;
1565 /* Support goto :label as well as goto label */
1566 if (*paramStart == ':') paramStart++;
1568 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1569 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1571 while (isspaceW (*str)) str++;
1575 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1578 /* ignore space at the end */
1580 if (lstrcmpiW (current, paramStart) == 0) return;
1583 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1588 /*****************************************************************************
1591 * Push a directory onto the stack
1594 void WCMD_pushd (const WCHAR *command)
1596 struct env_stack *curdir;
1598 static const WCHAR parmD[] = {'/','D','\0'};
1600 if (strchrW(command, '/') != NULL) {
1601 SetLastError(ERROR_INVALID_PARAMETER);
1606 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1607 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1608 if( !curdir || !thisdir ) {
1611 WINE_ERR ("out of memory\n");
1615 /* Change directory using CD code with /D parameter */
1616 strcpyW(quals, parmD);
1617 GetCurrentDirectoryW (1024, thisdir);
1619 WCMD_setshow_default(command);
1625 curdir -> next = pushd_directories;
1626 curdir -> strings = thisdir;
1627 if (pushd_directories == NULL) {
1628 curdir -> u.stackdepth = 1;
1630 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1632 pushd_directories = curdir;
1637 /*****************************************************************************
1640 * Pop a directory from the stack
1643 void WCMD_popd (void) {
1644 struct env_stack *temp = pushd_directories;
1646 if (!pushd_directories)
1649 /* pop the old environment from the stack, and make it the current dir */
1650 pushd_directories = temp->next;
1651 SetCurrentDirectoryW(temp->strings);
1652 LocalFree (temp->strings);
1656 /****************************************************************************
1659 * Batch file conditional.
1661 * On entry, cmdlist will point to command containing the IF, and optionally
1662 * the first command to execute (if brackets not found)
1663 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1664 * If ('s were found, execute all within that bracket
1665 * Command may optionally be followed by an ELSE - need to skip instructions
1666 * in the else using the same logic
1668 * FIXME: Much more syntax checking needed!
1671 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1673 int negate; /* Negate condition */
1674 int test; /* Condition evaluation result */
1675 WCHAR condition[MAX_PATH], *command, *s;
1676 static const WCHAR notW[] = {'n','o','t','\0'};
1677 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1678 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1679 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1680 static const WCHAR eqeqW[] = {'=','=','\0'};
1681 static const WCHAR parmI[] = {'/','I','\0'};
1682 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1684 negate = !lstrcmpiW(param1,notW);
1685 strcpyW(condition, (negate ? param2 : param1));
1686 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1688 if (!lstrcmpiW (condition, errlvlW)) {
1689 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL, FALSE);
1691 long int param_int = strtolW(param, &endptr, 10);
1693 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1696 test = ((long int)errorlevel >= param_int);
1697 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1699 else if (!lstrcmpiW (condition, existW)) {
1700 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE))
1701 != INVALID_FILE_ATTRIBUTES);
1702 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1704 else if (!lstrcmpiW (condition, defdW)) {
1705 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE),
1707 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1709 else if ((s = strstrW (p, eqeqW))) {
1710 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1711 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1713 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd, FALSE);
1714 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd, FALSE);
1715 test = caseInsensitive
1716 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1717 leftPart, leftPartEnd-leftPart+1,
1718 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1719 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1720 leftPart, leftPartEnd-leftPart+1,
1721 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1722 WCMD_parameter(s, 1, &command, NULL, FALSE);
1725 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1729 /* Process rest of IF statement which is on the same line
1730 Note: This may process all or some of the cmdList (eg a GOTO) */
1731 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1734 /****************************************************************************
1737 * Move a file, directory tree or wildcarded set of files.
1740 void WCMD_move (void)
1743 WIN32_FIND_DATAW fd;
1745 WCHAR input[MAX_PATH];
1746 WCHAR output[MAX_PATH];
1748 WCHAR dir[MAX_PATH];
1749 WCHAR fname[MAX_PATH];
1750 WCHAR ext[MAX_PATH];
1752 if (param1[0] == 0x00) {
1753 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1757 /* If no destination supplied, assume current directory */
1758 if (param2[0] == 0x00) {
1759 strcpyW(param2, dotW);
1762 /* If 2nd parm is directory, then use original filename */
1763 /* Convert partial path to full path */
1764 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1765 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1766 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1767 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1769 /* Split into components */
1770 WCMD_splitpath(input, drive, dir, fname, ext);
1772 hff = FindFirstFileW(input, &fd);
1773 if (hff == INVALID_HANDLE_VALUE)
1777 WCHAR dest[MAX_PATH];
1778 WCHAR src[MAX_PATH];
1782 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1784 /* Build src & dest name */
1785 strcpyW(src, drive);
1788 /* See if dest is an existing directory */
1789 attribs = GetFileAttributesW(output);
1790 if (attribs != INVALID_FILE_ATTRIBUTES &&
1791 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1792 strcpyW(dest, output);
1793 strcatW(dest, slashW);
1794 strcatW(dest, fd.cFileName);
1796 strcpyW(dest, output);
1799 strcatW(src, fd.cFileName);
1801 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1802 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1804 /* If destination exists, prompt unless /Y supplied */
1805 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1807 WCHAR copycmd[MAXSTRING];
1810 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1811 if (strstrW (quals, parmNoY))
1813 else if (strstrW (quals, parmY))
1816 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1817 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1818 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1819 && ! lstrcmpiW (copycmd, parmY));
1822 /* Prompt if overwriting */
1826 /* Ask for confirmation */
1827 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1828 ok = WCMD_ask_confirm(question, FALSE, NULL);
1829 LocalFree(question);
1831 /* So delete the destination prior to the move */
1833 if (!DeleteFileW(dest)) {
1834 WCMD_print_error ();
1843 status = MoveFileW(src, dest);
1845 status = 1; /* Anything other than 0 to prevent error msg below */
1849 WCMD_print_error ();
1852 } while (FindNextFileW(hff, &fd) != 0);
1857 /****************************************************************************
1860 * Suspend execution of a batch script until a key is typed
1863 void WCMD_pause (void)
1869 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1871 have_console = GetConsoleMode(hIn, &oldmode);
1873 SetConsoleMode(hIn, 0);
1875 WCMD_output_asis(anykey);
1876 WCMD_ReadFile(hIn, &key, 1, &count);
1878 SetConsoleMode(hIn, oldmode);
1881 /****************************************************************************
1884 * Delete a directory.
1887 void WCMD_remove_dir (WCHAR *command) {
1890 int argsProcessed = 0;
1891 WCHAR *argN = command;
1892 static const WCHAR parmS[] = {'/','S','\0'};
1893 static const WCHAR parmQ[] = {'/','Q','\0'};
1895 /* Loop through all args */
1897 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
1898 if (argN && argN[0] != '/') {
1899 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1900 wine_dbgstr_w(quals));
1903 /* If subdirectory search not supplied, just try to remove
1904 and report error if it fails (eg if it contains a file) */
1905 if (strstrW (quals, parmS) == NULL) {
1906 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1908 /* Otherwise use ShFileOp to recursively remove a directory */
1911 SHFILEOPSTRUCTW lpDir;
1914 if (strstrW (quals, parmQ) == NULL) {
1916 WCHAR question[MAXSTRING];
1917 static const WCHAR fmt[] = {'%','s',' ','\0'};
1919 /* Ask for confirmation */
1920 wsprintfW(question, fmt, thisArg);
1921 ok = WCMD_ask_confirm(question, TRUE, NULL);
1923 /* Abort if answer is 'N' */
1930 lpDir.pFrom = thisArg;
1931 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1932 lpDir.wFunc = FO_DELETE;
1934 /* SHFileOperationW needs file list with a double null termination */
1935 thisArg[lstrlenW(thisArg) + 1] = 0x00;
1937 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1942 /* Handle no valid args */
1943 if (argsProcessed == 0) {
1944 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1950 /****************************************************************************
1956 void WCMD_rename (void)
1960 WIN32_FIND_DATAW fd;
1961 WCHAR input[MAX_PATH];
1962 WCHAR *dotDst = NULL;
1964 WCHAR dir[MAX_PATH];
1965 WCHAR fname[MAX_PATH];
1966 WCHAR ext[MAX_PATH];
1970 /* Must be at least two args */
1971 if (param1[0] == 0x00 || param2[0] == 0x00) {
1972 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1977 /* Destination cannot contain a drive letter or directory separator */
1978 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
1979 SetLastError(ERROR_INVALID_PARAMETER);
1985 /* Convert partial path to full path */
1986 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1987 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1988 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1989 dotDst = strchrW(param2, '.');
1991 /* Split into components */
1992 WCMD_splitpath(input, drive, dir, fname, ext);
1994 hff = FindFirstFileW(input, &fd);
1995 if (hff == INVALID_HANDLE_VALUE)
1999 WCHAR dest[MAX_PATH];
2000 WCHAR src[MAX_PATH];
2001 WCHAR *dotSrc = NULL;
2004 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2006 /* FIXME: If dest name or extension is *, replace with filename/ext
2007 part otherwise use supplied name. This supports:
2009 ren jim.* fred.* etc
2010 However, windows has a more complex algorithm supporting eg
2011 ?'s and *'s mid name */
2012 dotSrc = strchrW(fd.cFileName, '.');
2014 /* Build src & dest name */
2015 strcpyW(src, drive);
2018 dirLen = strlenW(src);
2019 strcatW(src, fd.cFileName);
2022 if (param2[0] == '*') {
2023 strcatW(dest, fd.cFileName);
2024 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
2026 strcatW(dest, param2);
2027 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
2030 /* Build Extension */
2031 if (dotDst && (*(dotDst+1)=='*')) {
2032 if (dotSrc) strcatW(dest, dotSrc);
2033 } else if (dotDst) {
2034 if (dotDst) strcatW(dest, dotDst);
2037 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2038 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2040 status = MoveFileW(src, dest);
2043 WCMD_print_error ();
2046 } while (FindNextFileW(hff, &fd) != 0);
2051 /*****************************************************************************
2054 * Make a copy of the environment.
2056 static WCHAR *WCMD_dupenv( const WCHAR *env )
2066 len += (strlenW(&env[len]) + 1);
2068 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
2071 WINE_ERR("out of memory\n");
2074 memcpy (env_copy, env, len*sizeof (WCHAR));
2080 /*****************************************************************************
2083 * setlocal pushes the environment onto a stack
2084 * Save the environment as unicode so we don't screw anything up.
2086 void WCMD_setlocal (const WCHAR *s) {
2088 struct env_stack *env_copy;
2089 WCHAR cwd[MAX_PATH];
2091 /* setlocal does nothing outside of batch programs */
2092 if (!context) return;
2094 /* DISABLEEXTENSIONS ignored */
2096 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2099 WINE_ERR ("out of memory\n");
2103 env = GetEnvironmentStringsW ();
2104 env_copy->strings = WCMD_dupenv (env);
2105 if (env_copy->strings)
2107 env_copy->batchhandle = context->h;
2108 env_copy->next = saved_environment;
2109 saved_environment = env_copy;
2111 /* Save the current drive letter */
2112 GetCurrentDirectoryW(MAX_PATH, cwd);
2113 env_copy->u.cwd = cwd[0];
2116 LocalFree (env_copy);
2118 FreeEnvironmentStringsW (env);
2122 /*****************************************************************************
2125 * endlocal pops the environment off a stack
2126 * Note: When searching for '=', search from WCHAR position 1, to handle
2127 * special internal environment variables =C:, =D: etc
2129 void WCMD_endlocal (void) {
2130 WCHAR *env, *old, *p;
2131 struct env_stack *temp;
2134 /* setlocal does nothing outside of batch programs */
2135 if (!context) return;
2137 /* setlocal needs a saved environment from within the same context (batch
2138 program) as it was saved in */
2139 if (!saved_environment || saved_environment->batchhandle != context->h)
2142 /* pop the old environment from the stack */
2143 temp = saved_environment;
2144 saved_environment = temp->next;
2146 /* delete the current environment, totally */
2147 env = GetEnvironmentStringsW ();
2148 old = WCMD_dupenv (GetEnvironmentStringsW ());
2151 n = strlenW(&old[len]) + 1;
2152 p = strchrW(&old[len] + 1, '=');
2156 SetEnvironmentVariableW (&old[len], NULL);
2161 FreeEnvironmentStringsW (env);
2163 /* restore old environment */
2164 env = temp->strings;
2167 n = strlenW(&env[len]) + 1;
2168 p = strchrW(&env[len] + 1, '=');
2172 SetEnvironmentVariableW (&env[len], p);
2177 /* Restore current drive letter */
2178 if (IsCharAlphaW(temp->u.cwd)) {
2180 WCHAR cwd[MAX_PATH];
2181 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2183 wsprintfW(envvar, fmt, temp->u.cwd);
2184 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2185 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2186 SetCurrentDirectoryW(cwd);
2194 /*****************************************************************************
2195 * WCMD_setshow_default
2197 * Set/Show the current default directory
2200 void WCMD_setshow_default (const WCHAR *command) {
2206 WIN32_FIND_DATAW fd;
2208 static const WCHAR parmD[] = {'/','D','\0'};
2210 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2212 /* Skip /D and trailing whitespace if on the front of the command line */
2213 if (CompareStringW(LOCALE_USER_DEFAULT,
2214 NORM_IGNORECASE | SORT_STRINGSORT,
2215 command, 2, parmD, -1) == CSTR_EQUAL) {
2217 while (*command && (*command==' ' || *command=='\t'))
2221 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2222 if (strlenW(command) == 0) {
2223 strcatW (cwd, newlineW);
2224 WCMD_output_asis (cwd);
2227 /* Remove any double quotes, which may be in the
2228 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2231 if (*command != '"') *pos++ = *command;
2234 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2238 /* Search for appropriate directory */
2239 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2240 hff = FindFirstFileW(string, &fd);
2241 if (hff != INVALID_HANDLE_VALUE) {
2243 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2244 WCHAR fpath[MAX_PATH];
2246 WCHAR dir[MAX_PATH];
2247 WCHAR fname[MAX_PATH];
2248 WCHAR ext[MAX_PATH];
2249 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2251 /* Convert path into actual directory spec */
2252 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2253 WCMD_splitpath(fpath, drive, dir, fname, ext);
2256 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2259 } while (FindNextFileW(hff, &fd) != 0);
2263 /* Change to that directory */
2264 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2266 status = SetCurrentDirectoryW(string);
2269 WCMD_print_error ();
2273 /* Save away the actual new directory, to store as current location */
2274 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2276 /* Restore old directory if drive letter would change, and
2277 CD x:\directory /D (or pushd c:\directory) not supplied */
2278 if ((strstrW(quals, parmD) == NULL) &&
2279 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2280 SetCurrentDirectoryW(cwd);
2284 /* Set special =C: type environment variable, for drive letter of
2285 change of directory, even if path was restored due to missing
2286 /D (allows changing drive letter when not resident on that
2288 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2290 strcpyW(env, equalW);
2291 memcpy(env+1, string, 2 * sizeof(WCHAR));
2293 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2294 SetEnvironmentVariableW(env, string);
2301 /****************************************************************************
2304 * Set/Show the system date
2305 * FIXME: Can't change date yet
2308 void WCMD_setshow_date (void) {
2310 WCHAR curdate[64], buffer[64];
2312 static const WCHAR parmT[] = {'/','T','\0'};
2314 if (strlenW(param1) == 0) {
2315 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2316 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2317 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2318 if (strstrW (quals, parmT) == NULL) {
2319 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2320 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2322 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2326 else WCMD_print_error ();
2329 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2333 /****************************************************************************
2335 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
2338 static int WCMD_compare( const void *a, const void *b )
2341 const WCHAR * const *str_a = a, * const *str_b = b;
2342 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2343 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
2344 if( r == CSTR_LESS_THAN ) return -1;
2345 if( r == CSTR_GREATER_THAN ) return 1;
2349 /****************************************************************************
2350 * WCMD_setshow_sortenv
2352 * sort variables into order for display
2353 * Optionally only display those who start with a stub
2354 * returns the count displayed
2356 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2358 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2361 if (stub) stublen = strlenW(stub);
2363 /* count the number of strings, and the total length */
2365 len += (strlenW(&s[len]) + 1);
2369 /* add the strings to an array */
2370 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2374 for( i=1; i<count; i++ )
2375 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2377 /* sort the array */
2378 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2381 for( i=0; i<count; i++ ) {
2382 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2383 NORM_IGNORECASE | SORT_STRINGSORT,
2384 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2385 /* Don't display special internal variables */
2386 if (str[i][0] != '=') {
2387 WCMD_output_asis(str[i]);
2388 WCMD_output_asis(newlineW);
2395 return displayedcount;
2398 /****************************************************************************
2401 * Set/Show the environment variables
2404 void WCMD_setshow_env (WCHAR *s) {
2409 static const WCHAR parmP[] = {'/','P','\0'};
2411 if (param1[0] == 0x00 && quals[0] == 0x00) {
2412 env = GetEnvironmentStringsW();
2413 WCMD_setshow_sortenv( env, NULL );
2417 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2418 if (CompareStringW(LOCALE_USER_DEFAULT,
2419 NORM_IGNORECASE | SORT_STRINGSORT,
2420 s, 2, parmP, -1) == CSTR_EQUAL) {
2421 WCHAR string[MAXSTRING];
2425 while (*s && (*s==' ' || *s=='\t')) s++;
2427 WCMD_strip_quotes(s);
2429 /* If no parameter, or no '=' sign, return an error */
2430 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2431 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2435 /* Output the prompt */
2437 if (strlenW(p) != 0) WCMD_output_asis(p);
2439 /* Read the reply */
2440 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2442 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2443 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2444 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2445 wine_dbgstr_w(string));
2446 status = SetEnvironmentVariableW(s, string);
2453 WCMD_strip_quotes(s);
2454 p = strchrW (s, '=');
2456 env = GetEnvironmentStringsW();
2457 if (WCMD_setshow_sortenv( env, s ) == 0) {
2458 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2465 if (strlenW(p) == 0) p = NULL;
2466 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2468 status = SetEnvironmentVariableW(s, p);
2469 gle = GetLastError();
2470 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2472 } else if ((!status)) WCMD_print_error();
2473 else errorlevel = 0;
2477 /****************************************************************************
2480 * Set/Show the path environment variable
2483 void WCMD_setshow_path (const WCHAR *command) {
2487 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2488 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2490 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
2491 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2493 WCMD_output_asis ( pathEqW);
2494 WCMD_output_asis ( string);
2495 WCMD_output_asis ( newlineW);
2498 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2502 if (*command == '=') command++; /* Skip leading '=' */
2503 status = SetEnvironmentVariableW(pathW, command);
2504 if (!status) WCMD_print_error();
2508 /****************************************************************************
2509 * WCMD_setshow_prompt
2511 * Set or show the command prompt.
2514 void WCMD_setshow_prompt (void) {
2517 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2519 if (strlenW(param1) == 0) {
2520 SetEnvironmentVariableW(promptW, NULL);
2524 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2525 if (strlenW(s) == 0) {
2526 SetEnvironmentVariableW(promptW, NULL);
2528 else SetEnvironmentVariableW(promptW, s);
2532 /****************************************************************************
2535 * Set/Show the system time
2536 * FIXME: Can't change time yet
2539 void WCMD_setshow_time (void) {
2541 WCHAR curtime[64], buffer[64];
2544 static const WCHAR parmT[] = {'/','T','\0'};
2546 if (strlenW(param1) == 0) {
2548 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2549 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2550 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2551 if (strstrW (quals, parmT) == NULL) {
2552 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2553 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2555 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2559 else WCMD_print_error ();
2562 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2566 /****************************************************************************
2569 * Shift batch parameters.
2570 * Optional /n says where to start shifting (n=0-8)
2573 void WCMD_shift (const WCHAR *command) {
2576 if (context != NULL) {
2577 WCHAR *pos = strchrW(command, '/');
2582 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2583 start = (*(pos+1) - '0');
2585 SetLastError(ERROR_INVALID_PARAMETER);
2590 WINE_TRACE("Shifting variables, starting at %d\n", start);
2591 for (i=start;i<=8;i++) {
2592 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2594 context -> shift_count[9] = context -> shift_count[9] + 1;
2599 /****************************************************************************
2602 void WCMD_start(const WCHAR *command)
2604 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
2605 '\\','s','t','a','r','t','.','e','x','e',0};
2606 WCHAR file[MAX_PATH];
2609 PROCESS_INFORMATION pi;
2611 GetWindowsDirectoryW( file, MAX_PATH );
2612 strcatW( file, exeW );
2613 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
2614 strcpyW( cmdline, file );
2615 strcatW( cmdline, spaceW );
2616 strcatW( cmdline, command );
2618 memset( &st, 0, sizeof(STARTUPINFOW) );
2619 st.cb = sizeof(STARTUPINFOW);
2621 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
2623 WaitForSingleObject( pi.hProcess, INFINITE );
2624 GetExitCodeProcess( pi.hProcess, &errorlevel );
2625 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
2626 CloseHandle(pi.hProcess);
2627 CloseHandle(pi.hThread);
2631 SetLastError(ERROR_FILE_NOT_FOUND);
2632 WCMD_print_error ();
2635 HeapFree( GetProcessHeap(), 0, cmdline );
2638 /****************************************************************************
2641 * Set the console title
2643 void WCMD_title (const WCHAR *command) {
2644 SetConsoleTitleW(command);
2647 /****************************************************************************
2650 * Copy a file to standard output.
2653 void WCMD_type (WCHAR *command) {
2656 WCHAR *argN = command;
2657 BOOL writeHeaders = FALSE;
2659 if (param1[0] == 0x00) {
2660 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2664 if (param2[0] != 0x00) writeHeaders = TRUE;
2666 /* Loop through all args */
2669 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
2677 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2678 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2679 FILE_ATTRIBUTE_NORMAL, NULL);
2680 if (h == INVALID_HANDLE_VALUE) {
2681 WCMD_print_error ();
2682 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2686 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2687 WCMD_output(fmt, thisArg);
2689 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2690 if (count == 0) break; /* ReadFile reports success on EOF! */
2692 WCMD_output_asis (buffer);
2699 /****************************************************************************
2702 * Output either a file or stdin to screen in pages
2705 void WCMD_more (WCHAR *command) {
2708 WCHAR *argN = command;
2710 WCHAR moreStrPage[100];
2713 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2714 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2715 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2716 ')',' ','-','-','\n','\0'};
2717 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2719 /* Prefix the NLS more with '-- ', then load the text */
2721 strcpyW(moreStr, moreStart);
2722 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2723 (sizeof(moreStr)/sizeof(WCHAR))-3);
2725 if (param1[0] == 0x00) {
2727 /* Wine implements pipes via temporary files, and hence stdin is
2728 effectively reading from the file. This means the prompts for
2729 more are satisfied by the next line from the input (file). To
2730 avoid this, ensure stdin is to the console */
2731 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2732 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2733 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2734 FILE_ATTRIBUTE_NORMAL, 0);
2735 WINE_TRACE("No parms - working probably in pipe mode\n");
2736 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2738 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2739 once you get in this bit unless due to a pipe, its going to end badly... */
2740 wsprintfW(moreStrPage, moreFmt, moreStr);
2742 WCMD_enter_paged_mode(moreStrPage);
2743 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2744 if (count == 0) break; /* ReadFile reports success on EOF! */
2746 WCMD_output_asis (buffer);
2748 WCMD_leave_paged_mode();
2750 /* Restore stdin to what it was */
2751 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2752 CloseHandle(hConIn);
2756 BOOL needsPause = FALSE;
2758 /* Loop through all args */
2759 WINE_TRACE("Parms supplied - working through each file\n");
2760 WCMD_enter_paged_mode(moreStrPage);
2763 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
2771 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2772 WCMD_leave_paged_mode();
2773 WCMD_output_asis(moreStrPage);
2774 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2775 WCMD_enter_paged_mode(moreStrPage);
2779 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2780 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2781 FILE_ATTRIBUTE_NORMAL, NULL);
2782 if (h == INVALID_HANDLE_VALUE) {
2783 WCMD_print_error ();
2784 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2788 ULONG64 fileLen = 0;
2789 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2791 /* Get the file size */
2792 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2793 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2796 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2797 if (count == 0) break; /* ReadFile reports success on EOF! */
2801 /* Update % count (would be used in WCMD_output_asis as prompt) */
2802 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2804 WCMD_output_asis (buffer);
2810 WCMD_leave_paged_mode();
2814 /****************************************************************************
2817 * Display verify flag.
2818 * FIXME: We don't actually do anything with the verify flag other than toggle
2822 void WCMD_verify (const WCHAR *command) {
2826 count = strlenW(command);
2828 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2829 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2832 if (lstrcmpiW(command, onW) == 0) {
2836 else if (lstrcmpiW(command, offW) == 0) {
2837 verify_mode = FALSE;
2840 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2843 /****************************************************************************
2846 * Display version info.
2849 void WCMD_version (void) {
2851 WCMD_output_asis (version_string);
2855 /****************************************************************************
2858 * Display volume information (set_label = FALSE)
2859 * Additionally set volume label (set_label = TRUE)
2860 * Returns 1 on success, 0 otherwise
2863 int WCMD_volume(BOOL set_label, const WCHAR *path)
2865 DWORD count, serial;
2866 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2869 if (strlenW(path) == 0) {
2870 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2872 WCMD_print_error ();
2875 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2876 &serial, NULL, NULL, NULL, 0);
2879 static const WCHAR fmt[] = {'%','s','\\','\0'};
2880 if ((path[1] != ':') || (strlenW(path) != 2)) {
2881 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2884 wsprintfW (curdir, fmt, path);
2885 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2890 WCMD_print_error ();
2893 if (label[0] != '\0') {
2894 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
2898 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
2901 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
2902 HIWORD(serial), LOWORD(serial));
2904 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2905 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2907 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2908 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2910 if (strlenW(path) != 0) {
2911 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2914 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2920 /**************************************************************************
2923 * Exit either the process, or just this batch program
2927 void WCMD_exit (CMD_LIST **cmdList) {
2929 static const WCHAR parmB[] = {'/','B','\0'};
2930 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2932 if (context && lstrcmpiW(quals, parmB) == 0) {
2934 context -> skip_rest = TRUE;
2942 /*****************************************************************************
2945 * Lists or sets file associations (assoc = TRUE)
2946 * Lists or sets file types (assoc = FALSE)
2948 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2951 DWORD accessOptions = KEY_READ;
2953 LONG rc = ERROR_SUCCESS;
2954 WCHAR keyValue[MAXSTRING];
2955 DWORD valueLen = MAXSTRING;
2957 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2958 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2960 /* See if parameter includes '=' */
2962 newValue = strchrW(command, '=');
2963 if (newValue) accessOptions |= KEY_WRITE;
2965 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2966 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2967 accessOptions, &key) != ERROR_SUCCESS) {
2968 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2972 /* If no parameters then list all associations */
2973 if (*command == 0x00) {
2976 /* Enumerate all the keys */
2977 while (rc != ERROR_NO_MORE_ITEMS) {
2978 WCHAR keyName[MAXSTRING];
2981 /* Find the next value */
2982 nameLen = MAXSTRING;
2983 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2985 if (rc == ERROR_SUCCESS) {
2987 /* Only interested in extension ones if assoc, or others
2989 if ((keyName[0] == '.' && assoc) ||
2990 (!(keyName[0] == '.') && (!assoc)))
2992 WCHAR subkey[MAXSTRING];
2993 strcpyW(subkey, keyName);
2994 if (!assoc) strcatW(subkey, shOpCmdW);
2996 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2998 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2999 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3000 WCMD_output_asis(keyName);
3001 WCMD_output_asis(equalW);
3002 /* If no default value found, leave line empty after '=' */
3003 if (rc == ERROR_SUCCESS) {
3004 WCMD_output_asis(keyValue);
3006 WCMD_output_asis(newlineW);
3007 RegCloseKey(readKey);
3015 /* Parameter supplied - if no '=' on command line, its a query */
3016 if (newValue == NULL) {
3018 WCHAR subkey[MAXSTRING];
3020 /* Query terminates the parameter at the first space */
3021 strcpyW(keyValue, command);
3022 space = strchrW(keyValue, ' ');
3023 if (space) *space=0x00;
3025 /* Set up key name */
3026 strcpyW(subkey, keyValue);
3027 if (!assoc) strcatW(subkey, shOpCmdW);
3029 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3031 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3032 WCMD_output_asis(command);
3033 WCMD_output_asis(equalW);
3034 /* If no default value found, leave line empty after '=' */
3035 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
3036 WCMD_output_asis(newlineW);
3037 RegCloseKey(readKey);
3040 WCHAR msgbuffer[MAXSTRING];
3042 /* Load the translated 'File association not found' */
3044 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3046 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3048 WCMD_output_stderr(msgbuffer, keyValue);
3052 /* Not a query - its a set or clear of a value */
3055 WCHAR subkey[MAXSTRING];
3057 /* Get pointer to new value */
3061 /* Set up key name */
3062 strcpyW(subkey, command);
3063 if (!assoc) strcatW(subkey, shOpCmdW);
3065 /* If nothing after '=' then clear value - only valid for ASSOC */
3066 if (*newValue == 0x00) {
3068 if (assoc) rc = RegDeleteKeyW(key, command);
3069 if (assoc && rc == ERROR_SUCCESS) {
3070 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
3072 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
3077 WCHAR msgbuffer[MAXSTRING];
3079 /* Load the translated 'File association not found' */
3081 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
3082 sizeof(msgbuffer)/sizeof(WCHAR));
3084 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
3085 sizeof(msgbuffer)/sizeof(WCHAR));
3087 WCMD_output_stderr(msgbuffer, keyValue);
3091 /* It really is a set value = contents */
3093 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
3094 accessOptions, NULL, &readKey, NULL);
3095 if (rc == ERROR_SUCCESS) {
3096 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
3098 sizeof(WCHAR) * (strlenW(newValue) + 1));
3099 RegCloseKey(readKey);
3102 if (rc != ERROR_SUCCESS) {
3106 WCMD_output_asis(command);
3107 WCMD_output_asis(equalW);
3108 WCMD_output_asis(newValue);
3109 WCMD_output_asis(newlineW);
3119 /****************************************************************************
3122 * Colors the terminal screen.
3125 void WCMD_color (void) {
3127 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3128 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3130 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3131 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3135 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3141 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3146 /* Convert the color hex digits */
3147 if (param1[0] == 0x00) {
3148 color = defaultColor;
3150 color = strtoulW(param1, NULL, 16);
3153 /* Fail if fg == bg color */
3154 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3159 /* Set the current screen contents and ensure all future writes
3160 remain this color */
3161 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3162 SetConsoleTextAttribute(hStdOut, color);