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;
40 struct env_stack *pushd_directories;
41 const WCHAR dotW[] = {'.','\0'};
42 const WCHAR dotdotW[] = {'.','.','\0'};
43 const WCHAR nullW[] = {'\0'};
44 const WCHAR starW[] = {'*','\0'};
45 const WCHAR slashW[] = {'\\','\0'};
46 const WCHAR equalW[] = {'=','\0'};
47 const WCHAR inbuilt[][10] = {
48 {'C','A','L','L','\0'},
50 {'C','H','D','I','R','\0'},
52 {'C','O','P','Y','\0'},
53 {'C','T','T','Y','\0'},
54 {'D','A','T','E','\0'},
57 {'E','C','H','O','\0'},
58 {'E','R','A','S','E','\0'},
60 {'G','O','T','O','\0'},
61 {'H','E','L','P','\0'},
63 {'L','A','B','E','L','\0'},
65 {'M','K','D','I','R','\0'},
66 {'M','O','V','E','\0'},
67 {'P','A','T','H','\0'},
68 {'P','A','U','S','E','\0'},
69 {'P','R','O','M','P','T','\0'},
72 {'R','E','N','A','M','E','\0'},
74 {'R','M','D','I','R','\0'},
76 {'S','H','I','F','T','\0'},
77 {'S','T','A','R','T','\0'},
78 {'T','I','M','E','\0'},
79 {'T','I','T','L','E','\0'},
80 {'T','Y','P','E','\0'},
81 {'V','E','R','I','F','Y','\0'},
84 {'E','N','D','L','O','C','A','L','\0'},
85 {'S','E','T','L','O','C','A','L','\0'},
86 {'P','U','S','H','D','\0'},
87 {'P','O','P','D','\0'},
88 {'A','S','S','O','C','\0'},
89 {'C','O','L','O','R','\0'},
90 {'F','T','Y','P','E','\0'},
91 {'M','O','R','E','\0'},
92 {'C','H','O','I','C','E','\0'},
93 {'E','X','I','T','\0'}
95 static const WCHAR externals[][10] = {
96 {'A','T','T','R','I','B','\0'},
97 {'X','C','O','P','Y','\0'}
99 static const WCHAR fslashW[] = {'/','\0'};
100 static const WCHAR onW[] = {'O','N','\0'};
101 static const WCHAR offW[] = {'O','F','F','\0'};
102 static const WCHAR parmY[] = {'/','Y','\0'};
103 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
105 static HINSTANCE hinst;
106 static struct env_stack *saved_environment;
107 static BOOL verify_mode = FALSE;
109 /**************************************************************************
112 * Issue a message and ask for confirmation, waiting on a valid answer.
114 * Returns True if Y (or A) answer is selected
115 * If optionAll contains a pointer, ALL is allowed, and if answered
119 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
123 WCHAR confirm[MAXSTRING];
124 WCHAR options[MAXSTRING];
125 WCHAR Ybuffer[MAXSTRING];
126 WCHAR Nbuffer[MAXSTRING];
127 WCHAR Abuffer[MAXSTRING];
128 WCHAR answer[MAX_PATH] = {'\0'};
131 /* Load the translated valid answers */
133 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
134 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
135 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
136 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
137 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
138 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
140 /* Loop waiting on a valid answer */
145 WCMD_output_asis (message);
147 WCMD_output_asis (confirm);
148 WCMD_output_asis (options);
149 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
150 answer[0] = toupperW(answer[0]);
151 if (answer[0] == Ybuffer[0])
153 if (answer[0] == Nbuffer[0])
155 if (optionAll && answer[0] == Abuffer[0])
163 /****************************************************************************
166 * Clear the terminal screen.
169 void WCMD_clear_screen (void) {
171 /* Emulate by filling the screen from the top left to bottom right with
172 spaces, then moving the cursor to the top left afterwards */
173 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
174 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
176 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
181 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
185 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
186 SetConsoleCursorPosition(hStdOut, topLeft);
190 /****************************************************************************
193 * Change the default i/o device (ie redirect STDin/STDout).
196 void WCMD_change_tty (void) {
198 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
202 /****************************************************************************
207 void WCMD_choice (const WCHAR * command) {
209 static const WCHAR bellW[] = {7,0};
210 static const WCHAR commaW[] = {',',0};
211 static const WCHAR bracket_open[] = {'[',0};
212 static const WCHAR bracket_close[] = {']','?',0};
217 WCHAR *my_command = NULL;
218 WCHAR opt_default = 0;
219 DWORD opt_timeout = 0;
226 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
229 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
233 ptr = WCMD_skip_leading_spaces(my_command);
234 while (*ptr == '/') {
235 switch (toupperW(ptr[1])) {
238 /* the colon is optional */
242 if (!*ptr || isspaceW(*ptr)) {
243 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
244 HeapFree(GetProcessHeap(), 0, my_command);
248 /* remember the allowed keys (overwrite previous /C option) */
250 while (*ptr && (!isspaceW(*ptr)))
254 /* terminate allowed chars */
256 ptr = WCMD_skip_leading_spaces(&ptr[1]);
258 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
263 ptr = WCMD_skip_leading_spaces(&ptr[2]);
268 ptr = WCMD_skip_leading_spaces(&ptr[2]);
273 /* the colon is optional */
277 opt_default = *ptr++;
279 if (!opt_default || (*ptr != ',')) {
280 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
281 HeapFree(GetProcessHeap(), 0, my_command);
287 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
293 opt_timeout = atoiW(answer);
295 ptr = WCMD_skip_leading_spaces(ptr);
299 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
300 HeapFree(GetProcessHeap(), 0, my_command);
306 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
309 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
311 /* use default keys, when needed: localized versions of "Y"es and "No" */
313 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
314 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
319 /* print the question, when needed */
321 WCMD_output_asis(ptr);
325 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
329 /* print a list of all allowed answers inside brackets */
330 WCMD_output_asis(bracket_open);
333 while ((answer[0] = *ptr++)) {
334 WCMD_output_asis(answer);
336 WCMD_output_asis(commaW);
338 WCMD_output_asis(bracket_close);
343 /* FIXME: Add support for option /T */
344 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
347 answer[0] = toupperW(answer[0]);
349 ptr = strchrW(opt_c, answer[0]);
351 WCMD_output_asis(answer);
352 WCMD_output_asis(newlineW);
354 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
356 errorlevel = (ptr - opt_c) + 1;
357 WINE_TRACE("answer: %d\n", errorlevel);
358 HeapFree(GetProcessHeap(), 0, my_command);
363 /* key not allowed: play the bell */
364 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
365 WCMD_output_asis(bellW);
370 /****************************************************************************
373 * Copy a file or wildcarded set.
374 * FIXME: Add support for a+b+c type syntax
377 void WCMD_copy (void) {
382 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
384 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
385 BOOL copyToDir = FALSE;
386 WCHAR srcspec[MAX_PATH];
390 WCHAR fname[MAX_PATH];
393 if (param1[0] == 0x00) {
394 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
398 /* Convert source into full spec */
399 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
400 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
401 if (srcpath[strlenW(srcpath) - 1] == '\\')
402 srcpath[strlenW(srcpath) - 1] = '\0';
404 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
405 attribs = GetFileAttributesW(srcpath);
409 strcpyW(srcspec, srcpath);
411 /* If a directory, then add \* on the end when searching */
412 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
413 strcatW(srcpath, slashW);
414 strcatW(srcspec, slashW);
415 strcatW(srcspec, starW);
417 WCMD_splitpath(srcpath, drive, dir, fname, ext);
418 strcpyW(srcpath, drive);
419 strcatW(srcpath, dir);
422 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
424 /* If no destination supplied, assume current directory */
425 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
426 if (param2[0] == 0x00) {
427 strcpyW(param2, dotW);
430 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
431 if (outpath[strlenW(outpath) - 1] == '\\')
432 outpath[strlenW(outpath) - 1] = '\0';
433 attribs = GetFileAttributesW(outpath);
434 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
435 strcatW (outpath, slashW);
438 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
439 wine_dbgstr_w(outpath), copyToDir);
441 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
442 if (strstrW (quals, parmNoY))
444 else if (strstrW (quals, parmY))
447 /* By default, we will force the overwrite in batch mode and ask for
448 * confirmation in interactive mode. */
451 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
452 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
453 * default behavior. */
454 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
455 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
456 if (!lstrcmpiW (copycmd, parmY))
458 else if (!lstrcmpiW (copycmd, parmNoY))
463 /* Loop through all source files */
464 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
465 hff = FindFirstFileW(srcspec, &fd);
466 if (hff != INVALID_HANDLE_VALUE) {
468 WCHAR outname[MAX_PATH];
469 WCHAR srcname[MAX_PATH];
470 BOOL overwrite = force;
472 /* Destination is either supplied filename, or source name in
473 supplied destination directory */
474 strcpyW(outname, outpath);
475 if (copyToDir) strcatW(outname, fd.cFileName);
476 strcpyW(srcname, srcpath);
477 strcatW(srcname, fd.cFileName);
479 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
480 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
482 /* Skip . and .., and directories */
483 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
485 WINE_TRACE("Skipping directories\n");
488 /* Prompt before overwriting */
489 else if (!overwrite) {
490 attribs = GetFileAttributesW(outname);
491 if (attribs != INVALID_FILE_ATTRIBUTES) {
493 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
494 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
497 else overwrite = TRUE;
500 /* Do the copy as appropriate */
502 status = CopyFileW(srcname, outname, FALSE);
503 if (!status) WCMD_print_error ();
506 } while (FindNextFileW(hff, &fd) != 0);
513 /****************************************************************************
516 * Create a directory (and, if needed, any intermediate directories).
518 * Modifies its argument by replacing slashes temporarily with nulls.
521 static BOOL create_full_path(WCHAR* path)
525 /* don't mess with drive letter portion of path, if any */
530 /* Strip trailing slashes. */
531 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
534 /* Step through path, creating intermediate directories as needed. */
535 /* First component includes drive letter, if any. */
539 /* Skip to end of component */
540 while (*p == '\\') p++;
541 while (*p && *p != '\\') p++;
543 /* path is now the original full path */
544 return CreateDirectoryW(path, NULL);
546 /* Truncate path, create intermediate directory, and restore path */
548 rv = CreateDirectoryW(path, NULL);
550 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
557 void WCMD_create_dir (WCHAR *command) {
559 WCHAR *argN = command;
561 if (param1[0] == 0x00) {
562 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
565 /* Loop through all args */
567 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL);
569 if (!create_full_path(thisArg)) {
576 /* Parse the /A options given by the user on the commandline
577 * into a bitmask of wanted attributes (*wantSet),
578 * and a bitmask of unwanted attributes (*wantClear).
580 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
581 static const WCHAR parmA[] = {'/','A','\0'};
584 /* both are strictly 'out' parameters */
588 /* For each /A argument */
589 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
593 /* Skip optional : */
596 /* For each of the attribute specifier chars to this /A option */
597 for (; *p != 0 && *p != '/'; p++) {
606 /* Convert the attribute specifier to a bit in one of the masks */
608 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
609 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
610 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
611 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
613 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
623 /* If filename part of parameter is * or *.*,
624 * and neither /Q nor /P options were given,
625 * prompt the user whether to proceed.
626 * Returns FALSE if user says no, TRUE otherwise.
627 * *pPrompted is set to TRUE if the user is prompted.
628 * (If /P supplied, del will prompt for individual files later.)
630 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
631 static const WCHAR parmP[] = {'/','P','\0'};
632 static const WCHAR parmQ[] = {'/','Q','\0'};
634 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
635 static const WCHAR anyExt[]= {'.','*','\0'};
638 WCHAR fname[MAX_PATH];
640 WCHAR fpath[MAX_PATH];
642 /* Convert path into actual directory spec */
643 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
644 WCMD_splitpath(fpath, drive, dir, fname, ext);
646 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
647 if ((strcmpW(fname, starW) == 0) &&
648 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
650 WCHAR question[MAXSTRING];
651 static const WCHAR fmt[] = {'%','s',' ','\0'};
653 /* Caller uses this to suppress "file not found" warning later */
656 /* Ask for confirmation */
657 wsprintfW(question, fmt, fpath);
658 return WCMD_ask_confirm(question, TRUE, NULL);
661 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
665 /* Helper function for WCMD_delete().
666 * Deletes a single file, directory, or wildcard.
667 * If /S was given, does it recursively.
668 * Returns TRUE if a file was deleted.
670 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
672 static const WCHAR parmP[] = {'/','P','\0'};
673 static const WCHAR parmS[] = {'/','S','\0'};
674 static const WCHAR parmF[] = {'/','F','\0'};
676 DWORD unwanted_attrs;
678 WCHAR argCopy[MAX_PATH];
681 WCHAR fpath[MAX_PATH];
683 BOOL handleParm = TRUE;
685 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
687 strcpyW(argCopy, thisArg);
688 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
689 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
691 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
692 /* Skip this arg if user declines to delete *.* */
696 /* First, try to delete in the current directory */
697 hff = FindFirstFileW(argCopy, &fd);
698 if (hff == INVALID_HANDLE_VALUE) {
704 /* Support del <dirname> by just deleting all files dirname\* */
706 && (strchrW(argCopy,'*') == NULL)
707 && (strchrW(argCopy,'?') == NULL)
708 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
710 WCHAR modifiedParm[MAX_PATH];
711 static const WCHAR slashStar[] = {'\\','*','\0'};
713 strcpyW(modifiedParm, argCopy);
714 strcatW(modifiedParm, slashStar);
717 WCMD_delete_one(modifiedParm);
719 } else if (handleParm) {
721 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
722 strcpyW (fpath, argCopy);
724 p = strrchrW (fpath, '\\');
727 strcatW (fpath, fd.cFileName);
729 else strcpyW (fpath, fd.cFileName);
730 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
733 /* Handle attribute matching (/A) */
734 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
735 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
737 /* /P means prompt for each file */
738 if (ok && strstrW (quals, parmP) != NULL) {
741 /* Ask for confirmation */
742 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
743 ok = WCMD_ask_confirm(question, FALSE, NULL);
747 /* Only proceed if ok to */
750 /* If file is read only, and /A:r or /F supplied, delete it */
751 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
752 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
753 strstrW (quals, parmF) != NULL)) {
754 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
757 /* Now do the delete */
758 if (!DeleteFileW(fpath)) WCMD_print_error ();
762 } while (FindNextFileW(hff, &fd) != 0);
766 /* Now recurse into all subdirectories handling the parameter in the same way */
767 if (strstrW (quals, parmS) != NULL) {
769 WCHAR thisDir[MAX_PATH];
774 WCHAR fname[MAX_PATH];
777 /* Convert path into actual directory spec */
778 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
779 WCMD_splitpath(thisDir, drive, dir, fname, ext);
781 strcpyW(thisDir, drive);
782 strcatW(thisDir, dir);
783 cPos = strlenW(thisDir);
785 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
787 /* Append '*' to the directory */
789 thisDir[cPos+1] = 0x00;
791 hff = FindFirstFileW(thisDir, &fd);
793 /* Remove residual '*' */
794 thisDir[cPos] = 0x00;
796 if (hff != INVALID_HANDLE_VALUE) {
797 DIRECTORY_STACK *allDirs = NULL;
798 DIRECTORY_STACK *lastEntry = NULL;
801 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
802 (strcmpW(fd.cFileName, dotdotW) != 0) &&
803 (strcmpW(fd.cFileName, dotW) != 0)) {
805 DIRECTORY_STACK *nextDir;
806 WCHAR subParm[MAX_PATH];
808 /* Work out search parameter in sub dir */
809 strcpyW (subParm, thisDir);
810 strcatW (subParm, fd.cFileName);
811 strcatW (subParm, slashW);
812 strcatW (subParm, fname);
813 strcatW (subParm, ext);
814 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
816 /* Allocate memory, add to list */
817 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
818 if (allDirs == NULL) allDirs = nextDir;
819 if (lastEntry != NULL) lastEntry->next = nextDir;
821 nextDir->next = NULL;
822 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
823 (strlenW(subParm)+1) * sizeof(WCHAR));
824 strcpyW(nextDir->dirName, subParm);
826 } while (FindNextFileW(hff, &fd) != 0);
829 /* Go through each subdir doing the delete */
830 while (allDirs != NULL) {
831 DIRECTORY_STACK *tempDir;
833 tempDir = allDirs->next;
834 found |= WCMD_delete_one (allDirs->dirName);
836 HeapFree(GetProcessHeap(),0,allDirs->dirName);
837 HeapFree(GetProcessHeap(),0,allDirs);
846 /****************************************************************************
849 * Delete a file or wildcarded set.
852 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
853 * - Each set is a pattern, eg /ahr /as-r means
854 * readonly+hidden OR nonreadonly system files
855 * - The '-' applies to a single field, ie /a:-hr means read only
859 BOOL WCMD_delete (WCHAR *command) {
862 BOOL argsProcessed = FALSE;
863 BOOL foundAny = FALSE;
867 for (argno=0; ; argno++) {
872 thisArg = WCMD_parameter (command, argno, &argN, NULL);
874 break; /* no more parameters */
876 continue; /* skip options */
878 argsProcessed = TRUE;
879 found = WCMD_delete_one(thisArg);
882 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
887 /* Handle no valid args */
889 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
897 * Returns a trimmed version of s with all leading and trailing whitespace removed
901 static WCHAR *WCMD_strtrim(const WCHAR *s)
903 DWORD len = strlenW(s);
904 const WCHAR *start = s;
907 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
910 while (isspaceW(*start)) start++;
912 const WCHAR *end = s + len - 1;
913 while (end > start && isspaceW(*end)) end--;
914 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
915 result[end - start + 1] = '\0';
923 /****************************************************************************
926 * Echo input to the screen (or not). We don't try to emulate the bugs
927 * in DOS (try typing "ECHO ON AGAIN" for an example).
930 void WCMD_echo (const WCHAR *command)
933 const WCHAR *origcommand = command;
936 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
937 || command[0]==':' || command[0]==';')
940 trimmed = WCMD_strtrim(command);
941 if (!trimmed) return;
943 count = strlenW(trimmed);
944 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
945 && origcommand[0]!=';') {
946 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
947 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
951 if (lstrcmpiW(trimmed, onW) == 0)
953 else if (lstrcmpiW(trimmed, offW) == 0)
956 WCMD_output_asis (command);
957 WCMD_output_asis (newlineW);
959 HeapFree(GetProcessHeap(), 0, trimmed);
962 /*****************************************************************************
965 * Execute a command, and any && or bracketed follow on to the command. The
966 * first command to be executed may not be at the front of the
967 * commands->thiscommand string (eg. it may point after a DO or ELSE)
969 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
970 const WCHAR *variable, const WCHAR *value,
971 BOOL isIF, BOOL executecmds)
973 CMD_LIST *curPosition = *cmdList;
974 int myDepth = (*cmdList)->bracketDepth;
976 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
977 cmdList, wine_dbgstr_w(firstcmd),
978 wine_dbgstr_w(variable), wine_dbgstr_w(value),
981 /* Skip leading whitespace between condition and the command */
982 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
984 /* Process the first command, if there is one */
985 if (executecmds && firstcmd && *firstcmd) {
986 WCHAR *command = WCMD_strdupW(firstcmd);
987 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
988 HeapFree(GetProcessHeap(), 0, command);
992 /* If it didn't move the position, step to next command */
993 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
995 /* Process any other parts of the command */
997 BOOL processThese = executecmds;
1000 static const WCHAR ifElse[] = {'e','l','s','e'};
1002 /* execute all appropriate commands */
1003 curPosition = *cmdList;
1005 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1007 (*cmdList)->prevDelim,
1008 (*cmdList)->bracketDepth, myDepth);
1010 /* Execute any statements appended to the line */
1011 /* FIXME: Only if previous call worked for && or failed for || */
1012 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1013 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1014 if (processThese && (*cmdList)->command) {
1015 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1018 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1020 /* Execute any appended to the statement with (...) */
1021 } else if ((*cmdList)->bracketDepth > myDepth) {
1023 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1024 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1026 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1028 /* End of the command - does 'ELSE ' follow as the next command? */
1031 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1032 (*cmdList)->command)) {
1034 /* Swap between if and else processing */
1035 processThese = !processThese;
1037 /* Process the ELSE part */
1039 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1040 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1042 /* Skip leading whitespace between condition and the command */
1043 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1045 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1048 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1050 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1059 /**************************************************************************
1062 * Batch file loop processing.
1064 * On entry: cmdList contains the syntax up to the set
1065 * next cmdList and all in that bracket contain the set data
1066 * next cmdlist contains the DO cmd
1067 * following that is either brackets or && entries (as per if)
1071 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1073 WIN32_FIND_DATAW fd;
1076 static const WCHAR inW[] = {'i','n'};
1077 static const WCHAR doW[] = {'d','o'};
1078 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1084 BOOL expandDirs = FALSE;
1085 BOOL useNumbers = FALSE;
1086 BOOL doFileset = FALSE;
1087 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1089 CMD_LIST *thisCmdStart;
1092 /* Handle optional qualifiers (multiple are allowed) */
1093 while (*curPos && *curPos == '/') {
1094 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
1096 switch (toupperW(*curPos)) {
1097 case 'D': curPos++; expandDirs = TRUE; break;
1098 case 'L': curPos++; useNumbers = TRUE; break;
1100 /* Recursive is special case - /R can have an optional path following it */
1101 /* filenamesets are another special case - /F can have an optional options following it */
1105 BOOL isRecursive = (*curPos == 'R');
1110 /* Skip whitespace */
1112 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1114 /* Next parm is either qualifier, path/options or variable -
1115 only care about it if it is the path/options */
1116 if (*curPos && *curPos != '/' && *curPos != '%') {
1117 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
1119 static unsigned int once;
1120 if (!once++) WINE_FIXME("/F needs to handle options\n");
1126 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
1130 /* Skip whitespace between qualifiers */
1131 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1134 /* Skip whitespace before variable */
1135 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1137 /* Ensure line continues with variable */
1138 if (!*curPos || *curPos != '%') {
1139 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1143 /* Variable should follow */
1145 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
1146 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
1148 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1149 curPos = &curPos[i];
1151 /* Skip whitespace before IN */
1152 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1154 /* Ensure line continues with IN */
1156 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
1158 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1162 /* Save away where the set of data starts and the variable */
1163 thisDepth = (*cmdList)->bracketDepth;
1164 *cmdList = (*cmdList)->nextcommand;
1165 setStart = (*cmdList);
1167 /* Skip until the close bracket */
1168 WINE_TRACE("Searching %p as the set\n", *cmdList);
1170 (*cmdList)->command != NULL &&
1171 (*cmdList)->bracketDepth > thisDepth) {
1172 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1173 *cmdList = (*cmdList)->nextcommand;
1176 /* Skip the close bracket, if there is one */
1177 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1179 /* Syntax error if missing close bracket, or nothing following it
1180 and once we have the complete set, we expect a DO */
1181 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1182 if ((*cmdList == NULL)
1183 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1185 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1189 /* Save away the starting position for the commands (and offset for the
1191 cmdStart = *cmdList;
1193 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1197 /* Loop through all set entries */
1199 thisSet->command != NULL &&
1200 thisSet->bracketDepth >= thisDepth) {
1202 /* Loop through all entries on the same line */
1206 WINE_TRACE("Processing for set %p\n", thisSet);
1208 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1211 * If the parameter within the set has a wildcard then search for matching files
1212 * otherwise do a literal substitution.
1214 static const WCHAR wildcards[] = {'*','?','\0'};
1215 thisCmdStart = cmdStart;
1218 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1220 if (!useNumbers && !doFileset) {
1221 if (strpbrkW (item, wildcards)) {
1222 hff = FindFirstFileW(item, &fd);
1223 if (hff != INVALID_HANDLE_VALUE) {
1225 BOOL isDirectory = FALSE;
1227 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1229 /* Handle as files or dirs appropriately, but ignore . and .. */
1230 if (isDirectory == expandDirs &&
1231 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1232 (strcmpW(fd.cFileName, dotW) != 0))
1234 thisCmdStart = cmdStart;
1235 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1236 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1237 fd.cFileName, FALSE, TRUE);
1240 } while (FindNextFileW(hff, &fd) != 0);
1244 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1247 } else if (useNumbers) {
1248 /* Convert the first 3 numbers to signed longs and save */
1249 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1250 /* else ignore them! */
1252 /* Filesets - either a list of files, or a command to run and parse the output */
1253 } else if (doFileset && *itemStart != '"') {
1256 WCHAR temp_file[MAX_PATH];
1258 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1259 wine_dbgstr_w(item));
1261 /* If backquote or single quote, we need to launch that command
1262 and parse the results - use a temporary file */
1263 if (*itemStart == '`' || *itemStart == '\'') {
1265 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1266 static const WCHAR redirOut[] = {'>','%','s','\0'};
1267 static const WCHAR cmdW[] = {'C','M','D','\0'};
1269 /* Remove trailing character */
1270 itemStart[strlenW(itemStart)-1] = 0x00;
1272 /* Get temp filename */
1273 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1274 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1276 /* Execute program and redirect output */
1277 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1278 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1280 /* Open the file, read line by line and process */
1281 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1282 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1285 /* Open the file, read line by line and process */
1286 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1287 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1290 /* Process the input file */
1291 if (input == INVALID_HANDLE_VALUE) {
1292 WCMD_print_error ();
1293 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1295 return; /* FOR loop aborts at first failure here */
1299 WCHAR buffer[MAXSTRING] = {'\0'};
1300 WCHAR *where, *parm;
1302 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1304 /* Skip blank lines*/
1305 parm = WCMD_parameter (buffer, 0, &where, NULL);
1306 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1307 wine_dbgstr_w(buffer));
1310 /* FIXME: The following should be moved into its own routine and
1311 reused for the string literal parsing below */
1312 thisCmdStart = cmdStart;
1313 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1314 cmdEnd = thisCmdStart;
1320 CloseHandle (input);
1323 /* Delete the temporary file */
1324 if (*itemStart == '`' || *itemStart == '\'') {
1325 DeleteFileW(temp_file);
1328 /* Filesets - A string literal */
1329 } else if (doFileset && *itemStart == '"') {
1330 WCHAR buffer[MAXSTRING] = {'\0'};
1331 WCHAR *where, *parm;
1333 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1334 strcpyW(buffer, item);
1335 parm = WCMD_parameter (buffer, 0, &where, NULL);
1336 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1337 wine_dbgstr_w(buffer));
1340 /* FIXME: The following should be moved into its own routine and
1341 reused for the string literal parsing below */
1342 thisCmdStart = cmdStart;
1343 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1344 cmdEnd = thisCmdStart;
1348 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1349 cmdEnd = thisCmdStart;
1353 /* Move onto the next set line */
1354 thisSet = thisSet->nextcommand;
1357 /* If /L is provided, now run the for loop */
1360 static const WCHAR fmt[] = {'%','d','\0'};
1362 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1363 numbers[0], numbers[2], numbers[1]);
1365 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
1368 sprintfW(thisNum, fmt, i);
1369 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1371 thisCmdStart = cmdStart;
1372 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1375 /* Now skip over the subsequent commands if we did not perform the for loop */
1376 if (thisCmdStart == cmdStart) {
1377 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
1378 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, FALSE);
1380 cmdEnd = thisCmdStart;
1383 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1384 all processing, OR it should be pointing to the end of && processing OR
1385 it should be pointing at the NULL end of bracket for the DO. The return
1386 value needs to be the NEXT command to execute, which it either is, or
1387 we need to step over the closing bracket */
1389 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1392 /**************************************************************************
1395 * Simple on-line help. Help text is stored in the resource file.
1398 void WCMD_give_help (const WCHAR *command)
1402 command = WCMD_skip_leading_spaces((WCHAR*) command);
1403 if (strlenW(command) == 0) {
1404 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1407 /* Display help message for builtin commands */
1408 for (i=0; i<=WCMD_EXIT; i++) {
1409 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1410 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1411 WCMD_output_asis (WCMD_LoadMessage(i));
1415 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1416 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1417 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1418 command, -1, externals[i], -1) == CSTR_EQUAL) {
1420 static const WCHAR helpW[] = {' ', '/','?','\0'};
1421 strcpyW(cmd, command);
1422 strcatW(cmd, helpW);
1423 WCMD_run_program(cmd, FALSE);
1427 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1432 /****************************************************************************
1435 * Batch file jump instruction. Not the most efficient algorithm ;-)
1436 * Prints error message if the specified label cannot be found - the file pointer is
1437 * then at EOF, effectively stopping the batch file.
1438 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1441 void WCMD_goto (CMD_LIST **cmdList) {
1443 WCHAR string[MAX_PATH];
1444 WCHAR current[MAX_PATH];
1446 /* Do not process any more parts of a processed multipart or multilines command */
1447 if (cmdList) *cmdList = NULL;
1449 if (context != NULL) {
1450 WCHAR *paramStart = param1, *str;
1451 static const WCHAR eofW[] = {':','e','o','f','\0'};
1453 if (param1[0] == 0x00) {
1454 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1458 /* Handle special :EOF label */
1459 if (lstrcmpiW (eofW, param1) == 0) {
1460 context -> skip_rest = TRUE;
1464 /* Support goto :label as well as goto label */
1465 if (*paramStart == ':') paramStart++;
1467 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1468 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1470 while (isspaceW (*str)) str++;
1474 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1477 /* ignore space at the end */
1479 if (lstrcmpiW (current, paramStart) == 0) return;
1482 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1487 /*****************************************************************************
1490 * Push a directory onto the stack
1493 void WCMD_pushd (const WCHAR *command)
1495 struct env_stack *curdir;
1497 static const WCHAR parmD[] = {'/','D','\0'};
1499 if (strchrW(command, '/') != NULL) {
1500 SetLastError(ERROR_INVALID_PARAMETER);
1505 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1506 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1507 if( !curdir || !thisdir ) {
1510 WINE_ERR ("out of memory\n");
1514 /* Change directory using CD code with /D parameter */
1515 strcpyW(quals, parmD);
1516 GetCurrentDirectoryW (1024, thisdir);
1518 WCMD_setshow_default(command);
1524 curdir -> next = pushd_directories;
1525 curdir -> strings = thisdir;
1526 if (pushd_directories == NULL) {
1527 curdir -> u.stackdepth = 1;
1529 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1531 pushd_directories = curdir;
1536 /*****************************************************************************
1539 * Pop a directory from the stack
1542 void WCMD_popd (void) {
1543 struct env_stack *temp = pushd_directories;
1545 if (!pushd_directories)
1548 /* pop the old environment from the stack, and make it the current dir */
1549 pushd_directories = temp->next;
1550 SetCurrentDirectoryW(temp->strings);
1551 LocalFree (temp->strings);
1555 /****************************************************************************
1558 * Batch file conditional.
1560 * On entry, cmdlist will point to command containing the IF, and optionally
1561 * the first command to execute (if brackets not found)
1562 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1563 * If ('s were found, execute all within that bracket
1564 * Command may optionally be followed by an ELSE - need to skip instructions
1565 * in the else using the same logic
1567 * FIXME: Much more syntax checking needed!
1570 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1572 int negate; /* Negate condition */
1573 int test; /* Condition evaluation result */
1574 WCHAR condition[MAX_PATH], *command, *s;
1575 static const WCHAR notW[] = {'n','o','t','\0'};
1576 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1577 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1578 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1579 static const WCHAR eqeqW[] = {'=','=','\0'};
1580 static const WCHAR parmI[] = {'/','I','\0'};
1581 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1583 negate = !lstrcmpiW(param1,notW);
1584 strcpyW(condition, (negate ? param2 : param1));
1585 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1587 if (!lstrcmpiW (condition, errlvlW)) {
1588 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL);
1590 long int param_int = strtolW(param, &endptr, 10);
1592 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1595 test = ((long int)errorlevel >= param_int);
1596 WCMD_parameter(p, 2+negate, &command, NULL);
1598 else if (!lstrcmpiW (condition, existW)) {
1599 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1600 WCMD_parameter(p, 2+negate, &command, NULL);
1602 else if (!lstrcmpiW (condition, defdW)) {
1603 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1604 WCMD_parameter(p, 2+negate, &command, NULL);
1606 else if ((s = strstrW (p, eqeqW))) {
1607 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1608 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1610 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1611 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1612 test = caseInsensitive
1613 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1614 leftPart, leftPartEnd-leftPart+1,
1615 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1616 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1617 leftPart, leftPartEnd-leftPart+1,
1618 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1619 WCMD_parameter(s, 1, &command, NULL);
1622 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1626 /* Process rest of IF statement which is on the same line
1627 Note: This may process all or some of the cmdList (eg a GOTO) */
1628 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1631 /****************************************************************************
1634 * Move a file, directory tree or wildcarded set of files.
1637 void WCMD_move (void)
1640 WIN32_FIND_DATAW fd;
1642 WCHAR input[MAX_PATH];
1643 WCHAR output[MAX_PATH];
1645 WCHAR dir[MAX_PATH];
1646 WCHAR fname[MAX_PATH];
1647 WCHAR ext[MAX_PATH];
1649 if (param1[0] == 0x00) {
1650 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1654 /* If no destination supplied, assume current directory */
1655 if (param2[0] == 0x00) {
1656 strcpyW(param2, dotW);
1659 /* If 2nd parm is directory, then use original filename */
1660 /* Convert partial path to full path */
1661 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1662 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1663 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1664 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1666 /* Split into components */
1667 WCMD_splitpath(input, drive, dir, fname, ext);
1669 hff = FindFirstFileW(input, &fd);
1670 if (hff == INVALID_HANDLE_VALUE)
1674 WCHAR dest[MAX_PATH];
1675 WCHAR src[MAX_PATH];
1679 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1681 /* Build src & dest name */
1682 strcpyW(src, drive);
1685 /* See if dest is an existing directory */
1686 attribs = GetFileAttributesW(output);
1687 if (attribs != INVALID_FILE_ATTRIBUTES &&
1688 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1689 strcpyW(dest, output);
1690 strcatW(dest, slashW);
1691 strcatW(dest, fd.cFileName);
1693 strcpyW(dest, output);
1696 strcatW(src, fd.cFileName);
1698 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1699 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1701 /* If destination exists, prompt unless /Y supplied */
1702 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1704 WCHAR copycmd[MAXSTRING];
1707 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1708 if (strstrW (quals, parmNoY))
1710 else if (strstrW (quals, parmY))
1713 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1714 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1715 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1716 && ! lstrcmpiW (copycmd, parmY));
1719 /* Prompt if overwriting */
1723 /* Ask for confirmation */
1724 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1725 ok = WCMD_ask_confirm(question, FALSE, NULL);
1726 LocalFree(question);
1728 /* So delete the destination prior to the move */
1730 if (!DeleteFileW(dest)) {
1731 WCMD_print_error ();
1740 status = MoveFileW(src, dest);
1742 status = 1; /* Anything other than 0 to prevent error msg below */
1746 WCMD_print_error ();
1749 } while (FindNextFileW(hff, &fd) != 0);
1754 /****************************************************************************
1757 * Suspend execution of a batch script until a key is typed
1760 void WCMD_pause (void)
1766 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1768 have_console = GetConsoleMode(hIn, &oldmode);
1770 SetConsoleMode(hIn, 0);
1772 WCMD_output_asis(anykey);
1773 WCMD_ReadFile(hIn, &key, 1, &count);
1775 SetConsoleMode(hIn, oldmode);
1778 /****************************************************************************
1781 * Delete a directory.
1784 void WCMD_remove_dir (WCHAR *command) {
1787 int argsProcessed = 0;
1788 WCHAR *argN = command;
1789 static const WCHAR parmS[] = {'/','S','\0'};
1790 static const WCHAR parmQ[] = {'/','Q','\0'};
1792 /* Loop through all args */
1794 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1795 if (argN && argN[0] != '/') {
1796 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1797 wine_dbgstr_w(quals));
1800 /* If subdirectory search not supplied, just try to remove
1801 and report error if it fails (eg if it contains a file) */
1802 if (strstrW (quals, parmS) == NULL) {
1803 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1805 /* Otherwise use ShFileOp to recursively remove a directory */
1808 SHFILEOPSTRUCTW lpDir;
1811 if (strstrW (quals, parmQ) == NULL) {
1813 WCHAR question[MAXSTRING];
1814 static const WCHAR fmt[] = {'%','s',' ','\0'};
1816 /* Ask for confirmation */
1817 wsprintfW(question, fmt, thisArg);
1818 ok = WCMD_ask_confirm(question, TRUE, NULL);
1820 /* Abort if answer is 'N' */
1827 lpDir.pFrom = thisArg;
1828 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1829 lpDir.wFunc = FO_DELETE;
1831 /* SHFileOperationW needs file list with a double null termination */
1832 thisArg[lstrlenW(thisArg) + 1] = 0x00;
1834 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1839 /* Handle no valid args */
1840 if (argsProcessed == 0) {
1841 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1847 /****************************************************************************
1853 void WCMD_rename (void)
1857 WIN32_FIND_DATAW fd;
1858 WCHAR input[MAX_PATH];
1859 WCHAR *dotDst = NULL;
1861 WCHAR dir[MAX_PATH];
1862 WCHAR fname[MAX_PATH];
1863 WCHAR ext[MAX_PATH];
1867 /* Must be at least two args */
1868 if (param1[0] == 0x00 || param2[0] == 0x00) {
1869 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1874 /* Destination cannot contain a drive letter or directory separator */
1875 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
1876 SetLastError(ERROR_INVALID_PARAMETER);
1882 /* Convert partial path to full path */
1883 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1884 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1885 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1886 dotDst = strchrW(param2, '.');
1888 /* Split into components */
1889 WCMD_splitpath(input, drive, dir, fname, ext);
1891 hff = FindFirstFileW(input, &fd);
1892 if (hff == INVALID_HANDLE_VALUE)
1896 WCHAR dest[MAX_PATH];
1897 WCHAR src[MAX_PATH];
1898 WCHAR *dotSrc = NULL;
1901 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1903 /* FIXME: If dest name or extension is *, replace with filename/ext
1904 part otherwise use supplied name. This supports:
1906 ren jim.* fred.* etc
1907 However, windows has a more complex algorithm supporting eg
1908 ?'s and *'s mid name */
1909 dotSrc = strchrW(fd.cFileName, '.');
1911 /* Build src & dest name */
1912 strcpyW(src, drive);
1915 dirLen = strlenW(src);
1916 strcatW(src, fd.cFileName);
1919 if (param2[0] == '*') {
1920 strcatW(dest, fd.cFileName);
1921 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1923 strcatW(dest, param2);
1924 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1927 /* Build Extension */
1928 if (dotDst && (*(dotDst+1)=='*')) {
1929 if (dotSrc) strcatW(dest, dotSrc);
1930 } else if (dotDst) {
1931 if (dotDst) strcatW(dest, dotDst);
1934 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1935 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1937 status = MoveFileW(src, dest);
1940 WCMD_print_error ();
1943 } while (FindNextFileW(hff, &fd) != 0);
1948 /*****************************************************************************
1951 * Make a copy of the environment.
1953 static WCHAR *WCMD_dupenv( const WCHAR *env )
1963 len += (strlenW(&env[len]) + 1);
1965 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1968 WINE_ERR("out of memory\n");
1971 memcpy (env_copy, env, len*sizeof (WCHAR));
1977 /*****************************************************************************
1980 * setlocal pushes the environment onto a stack
1981 * Save the environment as unicode so we don't screw anything up.
1983 void WCMD_setlocal (const WCHAR *s) {
1985 struct env_stack *env_copy;
1986 WCHAR cwd[MAX_PATH];
1988 /* DISABLEEXTENSIONS ignored */
1990 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1993 WINE_ERR ("out of memory\n");
1997 env = GetEnvironmentStringsW ();
1999 env_copy->strings = WCMD_dupenv (env);
2000 if (env_copy->strings)
2002 env_copy->next = saved_environment;
2003 saved_environment = env_copy;
2005 /* Save the current drive letter */
2006 GetCurrentDirectoryW(MAX_PATH, cwd);
2007 env_copy->u.cwd = cwd[0];
2010 LocalFree (env_copy);
2012 FreeEnvironmentStringsW (env);
2016 /*****************************************************************************
2019 * endlocal pops the environment off a stack
2020 * Note: When searching for '=', search from WCHAR position 1, to handle
2021 * special internal environment variables =C:, =D: etc
2023 void WCMD_endlocal (void) {
2024 WCHAR *env, *old, *p;
2025 struct env_stack *temp;
2028 if (!saved_environment)
2031 /* pop the old environment from the stack */
2032 temp = saved_environment;
2033 saved_environment = temp->next;
2035 /* delete the current environment, totally */
2036 env = GetEnvironmentStringsW ();
2037 old = WCMD_dupenv (GetEnvironmentStringsW ());
2040 n = strlenW(&old[len]) + 1;
2041 p = strchrW(&old[len] + 1, '=');
2045 SetEnvironmentVariableW (&old[len], NULL);
2050 FreeEnvironmentStringsW (env);
2052 /* restore old environment */
2053 env = temp->strings;
2056 n = strlenW(&env[len]) + 1;
2057 p = strchrW(&env[len] + 1, '=');
2061 SetEnvironmentVariableW (&env[len], p);
2066 /* Restore current drive letter */
2067 if (IsCharAlphaW(temp->u.cwd)) {
2069 WCHAR cwd[MAX_PATH];
2070 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2072 wsprintfW(envvar, fmt, temp->u.cwd);
2073 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2074 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2075 SetCurrentDirectoryW(cwd);
2083 /*****************************************************************************
2084 * WCMD_setshow_default
2086 * Set/Show the current default directory
2089 void WCMD_setshow_default (const WCHAR *command) {
2095 WIN32_FIND_DATAW fd;
2097 static const WCHAR parmD[] = {'/','D','\0'};
2099 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2101 /* Skip /D and trailing whitespace if on the front of the command line */
2102 if (CompareStringW(LOCALE_USER_DEFAULT,
2103 NORM_IGNORECASE | SORT_STRINGSORT,
2104 command, 2, parmD, -1) == CSTR_EQUAL) {
2106 while (*command && (*command==' ' || *command=='\t'))
2110 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2111 if (strlenW(command) == 0) {
2112 strcatW (cwd, newlineW);
2113 WCMD_output_asis (cwd);
2116 /* Remove any double quotes, which may be in the
2117 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2120 if (*command != '"') *pos++ = *command;
2123 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2127 /* Search for appropriate directory */
2128 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2129 hff = FindFirstFileW(string, &fd);
2130 if (hff != INVALID_HANDLE_VALUE) {
2132 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2133 WCHAR fpath[MAX_PATH];
2135 WCHAR dir[MAX_PATH];
2136 WCHAR fname[MAX_PATH];
2137 WCHAR ext[MAX_PATH];
2138 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2140 /* Convert path into actual directory spec */
2141 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2142 WCMD_splitpath(fpath, drive, dir, fname, ext);
2145 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2148 } while (FindNextFileW(hff, &fd) != 0);
2152 /* Change to that directory */
2153 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2155 status = SetCurrentDirectoryW(string);
2158 WCMD_print_error ();
2162 /* Save away the actual new directory, to store as current location */
2163 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2165 /* Restore old directory if drive letter would change, and
2166 CD x:\directory /D (or pushd c:\directory) not supplied */
2167 if ((strstrW(quals, parmD) == NULL) &&
2168 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2169 SetCurrentDirectoryW(cwd);
2173 /* Set special =C: type environment variable, for drive letter of
2174 change of directory, even if path was restored due to missing
2175 /D (allows changing drive letter when not resident on that
2177 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2179 strcpyW(env, equalW);
2180 memcpy(env+1, string, 2 * sizeof(WCHAR));
2182 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2183 SetEnvironmentVariableW(env, string);
2190 /****************************************************************************
2193 * Set/Show the system date
2194 * FIXME: Can't change date yet
2197 void WCMD_setshow_date (void) {
2199 WCHAR curdate[64], buffer[64];
2201 static const WCHAR parmT[] = {'/','T','\0'};
2203 if (strlenW(param1) == 0) {
2204 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2205 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2206 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2207 if (strstrW (quals, parmT) == NULL) {
2208 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2209 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2211 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2215 else WCMD_print_error ();
2218 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2222 /****************************************************************************
2225 static int WCMD_compare( const void *a, const void *b )
2228 const WCHAR * const *str_a = a, * const *str_b = b;
2229 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2230 *str_a, -1, *str_b, -1 );
2231 if( r == CSTR_LESS_THAN ) return -1;
2232 if( r == CSTR_GREATER_THAN ) return 1;
2236 /****************************************************************************
2237 * WCMD_setshow_sortenv
2239 * sort variables into order for display
2240 * Optionally only display those who start with a stub
2241 * returns the count displayed
2243 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2245 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2248 if (stub) stublen = strlenW(stub);
2250 /* count the number of strings, and the total length */
2252 len += (strlenW(&s[len]) + 1);
2256 /* add the strings to an array */
2257 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2261 for( i=1; i<count; i++ )
2262 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2264 /* sort the array */
2265 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2268 for( i=0; i<count; i++ ) {
2269 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2270 NORM_IGNORECASE | SORT_STRINGSORT,
2271 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2272 /* Don't display special internal variables */
2273 if (str[i][0] != '=') {
2274 WCMD_output_asis(str[i]);
2275 WCMD_output_asis(newlineW);
2282 return displayedcount;
2285 /****************************************************************************
2288 * Set/Show the environment variables
2291 void WCMD_setshow_env (WCHAR *s) {
2296 static const WCHAR parmP[] = {'/','P','\0'};
2298 if (param1[0] == 0x00 && quals[0] == 0x00) {
2299 env = GetEnvironmentStringsW();
2300 WCMD_setshow_sortenv( env, NULL );
2304 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2305 if (CompareStringW(LOCALE_USER_DEFAULT,
2306 NORM_IGNORECASE | SORT_STRINGSORT,
2307 s, 2, parmP, -1) == CSTR_EQUAL) {
2308 WCHAR string[MAXSTRING];
2312 while (*s && (*s==' ' || *s=='\t')) s++;
2314 WCMD_strip_quotes(s);
2316 /* If no parameter, or no '=' sign, return an error */
2317 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2318 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2322 /* Output the prompt */
2324 if (strlenW(p) != 0) WCMD_output_asis(p);
2326 /* Read the reply */
2327 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2329 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2330 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2331 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2332 wine_dbgstr_w(string));
2333 status = SetEnvironmentVariableW(s, string);
2340 WCMD_strip_quotes(s);
2341 p = strchrW (s, '=');
2343 env = GetEnvironmentStringsW();
2344 if (WCMD_setshow_sortenv( env, s ) == 0) {
2345 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2352 if (strlenW(p) == 0) p = NULL;
2353 status = SetEnvironmentVariableW(s, p);
2354 gle = GetLastError();
2355 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2357 } else if ((!status)) WCMD_print_error();
2361 /****************************************************************************
2364 * Set/Show the path environment variable
2367 void WCMD_setshow_path (const WCHAR *command) {
2371 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2372 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2374 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
2375 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2377 WCMD_output_asis ( pathEqW);
2378 WCMD_output_asis ( string);
2379 WCMD_output_asis ( newlineW);
2382 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2386 if (*command == '=') command++; /* Skip leading '=' */
2387 status = SetEnvironmentVariableW(pathW, command);
2388 if (!status) WCMD_print_error();
2392 /****************************************************************************
2393 * WCMD_setshow_prompt
2395 * Set or show the command prompt.
2398 void WCMD_setshow_prompt (void) {
2401 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2403 if (strlenW(param1) == 0) {
2404 SetEnvironmentVariableW(promptW, NULL);
2408 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2409 if (strlenW(s) == 0) {
2410 SetEnvironmentVariableW(promptW, NULL);
2412 else SetEnvironmentVariableW(promptW, s);
2416 /****************************************************************************
2419 * Set/Show the system time
2420 * FIXME: Can't change time yet
2423 void WCMD_setshow_time (void) {
2425 WCHAR curtime[64], buffer[64];
2428 static const WCHAR parmT[] = {'/','T','\0'};
2430 if (strlenW(param1) == 0) {
2432 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2433 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2434 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2435 if (strstrW (quals, parmT) == NULL) {
2436 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2437 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2439 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2443 else WCMD_print_error ();
2446 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2450 /****************************************************************************
2453 * Shift batch parameters.
2454 * Optional /n says where to start shifting (n=0-8)
2457 void WCMD_shift (const WCHAR *command) {
2460 if (context != NULL) {
2461 WCHAR *pos = strchrW(command, '/');
2466 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2467 start = (*(pos+1) - '0');
2469 SetLastError(ERROR_INVALID_PARAMETER);
2474 WINE_TRACE("Shifting variables, starting at %d\n", start);
2475 for (i=start;i<=8;i++) {
2476 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2478 context -> shift_count[9] = context -> shift_count[9] + 1;
2483 /****************************************************************************
2486 void WCMD_start(const WCHAR *command)
2488 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
2489 '\\','s','t','a','r','t','.','e','x','e',0};
2490 WCHAR file[MAX_PATH];
2493 PROCESS_INFORMATION pi;
2495 GetWindowsDirectoryW( file, MAX_PATH );
2496 strcatW( file, exeW );
2497 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
2498 strcpyW( cmdline, file );
2499 strcatW( cmdline, spaceW );
2500 strcatW( cmdline, command );
2502 memset( &st, 0, sizeof(STARTUPINFOW) );
2503 st.cb = sizeof(STARTUPINFOW);
2505 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
2507 WaitForSingleObject( pi.hProcess, INFINITE );
2508 GetExitCodeProcess( pi.hProcess, &errorlevel );
2509 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
2510 CloseHandle(pi.hProcess);
2511 CloseHandle(pi.hThread);
2515 SetLastError(ERROR_FILE_NOT_FOUND);
2516 WCMD_print_error ();
2519 HeapFree( GetProcessHeap(), 0, cmdline );
2522 /****************************************************************************
2525 * Set the console title
2527 void WCMD_title (const WCHAR *command) {
2528 SetConsoleTitleW(command);
2531 /****************************************************************************
2534 * Copy a file to standard output.
2537 void WCMD_type (WCHAR *command) {
2540 WCHAR *argN = command;
2541 BOOL writeHeaders = FALSE;
2543 if (param1[0] == 0x00) {
2544 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2548 if (param2[0] != 0x00) writeHeaders = TRUE;
2550 /* Loop through all args */
2553 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2561 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2562 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2563 FILE_ATTRIBUTE_NORMAL, NULL);
2564 if (h == INVALID_HANDLE_VALUE) {
2565 WCMD_print_error ();
2566 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2570 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2571 WCMD_output(fmt, thisArg);
2573 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2574 if (count == 0) break; /* ReadFile reports success on EOF! */
2576 WCMD_output_asis (buffer);
2583 /****************************************************************************
2586 * Output either a file or stdin to screen in pages
2589 void WCMD_more (WCHAR *command) {
2592 WCHAR *argN = command;
2594 WCHAR moreStrPage[100];
2597 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2598 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2599 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2600 ')',' ','-','-','\n','\0'};
2601 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2603 /* Prefix the NLS more with '-- ', then load the text */
2605 strcpyW(moreStr, moreStart);
2606 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2607 (sizeof(moreStr)/sizeof(WCHAR))-3);
2609 if (param1[0] == 0x00) {
2611 /* Wine implements pipes via temporary files, and hence stdin is
2612 effectively reading from the file. This means the prompts for
2613 more are satisfied by the next line from the input (file). To
2614 avoid this, ensure stdin is to the console */
2615 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2616 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2617 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2618 FILE_ATTRIBUTE_NORMAL, 0);
2619 WINE_TRACE("No parms - working probably in pipe mode\n");
2620 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2622 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2623 once you get in this bit unless due to a pipe, its going to end badly... */
2624 wsprintfW(moreStrPage, moreFmt, moreStr);
2626 WCMD_enter_paged_mode(moreStrPage);
2627 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2628 if (count == 0) break; /* ReadFile reports success on EOF! */
2630 WCMD_output_asis (buffer);
2632 WCMD_leave_paged_mode();
2634 /* Restore stdin to what it was */
2635 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2636 CloseHandle(hConIn);
2640 BOOL needsPause = FALSE;
2642 /* Loop through all args */
2643 WINE_TRACE("Parms supplied - working through each file\n");
2644 WCMD_enter_paged_mode(moreStrPage);
2647 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2655 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2656 WCMD_leave_paged_mode();
2657 WCMD_output_asis(moreStrPage);
2658 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2659 WCMD_enter_paged_mode(moreStrPage);
2663 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2664 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2665 FILE_ATTRIBUTE_NORMAL, NULL);
2666 if (h == INVALID_HANDLE_VALUE) {
2667 WCMD_print_error ();
2668 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2672 ULONG64 fileLen = 0;
2673 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2675 /* Get the file size */
2676 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2677 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2680 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2681 if (count == 0) break; /* ReadFile reports success on EOF! */
2685 /* Update % count (would be used in WCMD_output_asis as prompt) */
2686 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2688 WCMD_output_asis (buffer);
2694 WCMD_leave_paged_mode();
2698 /****************************************************************************
2701 * Display verify flag.
2702 * FIXME: We don't actually do anything with the verify flag other than toggle
2706 void WCMD_verify (const WCHAR *command) {
2710 count = strlenW(command);
2712 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2713 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2716 if (lstrcmpiW(command, onW) == 0) {
2720 else if (lstrcmpiW(command, offW) == 0) {
2721 verify_mode = FALSE;
2724 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2727 /****************************************************************************
2730 * Display version info.
2733 void WCMD_version (void) {
2735 WCMD_output_asis (version_string);
2739 /****************************************************************************
2742 * Display volume information (set_label = FALSE)
2743 * Additionally set volume label (set_label = TRUE)
2744 * Returns 1 on success, 0 otherwise
2747 int WCMD_volume(BOOL set_label, const WCHAR *path)
2749 DWORD count, serial;
2750 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2753 if (strlenW(path) == 0) {
2754 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2756 WCMD_print_error ();
2759 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2760 &serial, NULL, NULL, NULL, 0);
2763 static const WCHAR fmt[] = {'%','s','\\','\0'};
2764 if ((path[1] != ':') || (strlenW(path) != 2)) {
2765 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2768 wsprintfW (curdir, fmt, path);
2769 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2774 WCMD_print_error ();
2777 if (label[0] != '\0') {
2778 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
2782 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
2785 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
2786 HIWORD(serial), LOWORD(serial));
2788 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2789 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2791 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2792 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2794 if (strlenW(path) != 0) {
2795 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2798 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2804 /**************************************************************************
2807 * Exit either the process, or just this batch program
2811 void WCMD_exit (CMD_LIST **cmdList) {
2813 static const WCHAR parmB[] = {'/','B','\0'};
2814 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2816 if (context && lstrcmpiW(quals, parmB) == 0) {
2818 context -> skip_rest = TRUE;
2826 /*****************************************************************************
2829 * Lists or sets file associations (assoc = TRUE)
2830 * Lists or sets file types (assoc = FALSE)
2832 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2835 DWORD accessOptions = KEY_READ;
2837 LONG rc = ERROR_SUCCESS;
2838 WCHAR keyValue[MAXSTRING];
2839 DWORD valueLen = MAXSTRING;
2841 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2842 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2844 /* See if parameter includes '=' */
2846 newValue = strchrW(command, '=');
2847 if (newValue) accessOptions |= KEY_WRITE;
2849 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2850 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2851 accessOptions, &key) != ERROR_SUCCESS) {
2852 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2856 /* If no parameters then list all associations */
2857 if (*command == 0x00) {
2860 /* Enumerate all the keys */
2861 while (rc != ERROR_NO_MORE_ITEMS) {
2862 WCHAR keyName[MAXSTRING];
2865 /* Find the next value */
2866 nameLen = MAXSTRING;
2867 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2869 if (rc == ERROR_SUCCESS) {
2871 /* Only interested in extension ones if assoc, or others
2873 if ((keyName[0] == '.' && assoc) ||
2874 (!(keyName[0] == '.') && (!assoc)))
2876 WCHAR subkey[MAXSTRING];
2877 strcpyW(subkey, keyName);
2878 if (!assoc) strcatW(subkey, shOpCmdW);
2880 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2882 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2883 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2884 WCMD_output_asis(keyName);
2885 WCMD_output_asis(equalW);
2886 /* If no default value found, leave line empty after '=' */
2887 if (rc == ERROR_SUCCESS) {
2888 WCMD_output_asis(keyValue);
2890 WCMD_output_asis(newlineW);
2891 RegCloseKey(readKey);
2899 /* Parameter supplied - if no '=' on command line, its a query */
2900 if (newValue == NULL) {
2902 WCHAR subkey[MAXSTRING];
2904 /* Query terminates the parameter at the first space */
2905 strcpyW(keyValue, command);
2906 space = strchrW(keyValue, ' ');
2907 if (space) *space=0x00;
2909 /* Set up key name */
2910 strcpyW(subkey, keyValue);
2911 if (!assoc) strcatW(subkey, shOpCmdW);
2913 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2915 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2916 WCMD_output_asis(command);
2917 WCMD_output_asis(equalW);
2918 /* If no default value found, leave line empty after '=' */
2919 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2920 WCMD_output_asis(newlineW);
2921 RegCloseKey(readKey);
2924 WCHAR msgbuffer[MAXSTRING];
2926 /* Load the translated 'File association not found' */
2928 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2930 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2932 WCMD_output_stderr(msgbuffer, keyValue);
2936 /* Not a query - its a set or clear of a value */
2939 WCHAR subkey[MAXSTRING];
2941 /* Get pointer to new value */
2945 /* Set up key name */
2946 strcpyW(subkey, command);
2947 if (!assoc) strcatW(subkey, shOpCmdW);
2949 /* If nothing after '=' then clear value - only valid for ASSOC */
2950 if (*newValue == 0x00) {
2952 if (assoc) rc = RegDeleteKeyW(key, command);
2953 if (assoc && rc == ERROR_SUCCESS) {
2954 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2956 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2961 WCHAR msgbuffer[MAXSTRING];
2963 /* Load the translated 'File association not found' */
2965 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2966 sizeof(msgbuffer)/sizeof(WCHAR));
2968 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2969 sizeof(msgbuffer)/sizeof(WCHAR));
2971 WCMD_output_stderr(msgbuffer, keyValue);
2975 /* It really is a set value = contents */
2977 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2978 accessOptions, NULL, &readKey, NULL);
2979 if (rc == ERROR_SUCCESS) {
2980 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2982 sizeof(WCHAR) * (strlenW(newValue) + 1));
2983 RegCloseKey(readKey);
2986 if (rc != ERROR_SUCCESS) {
2990 WCMD_output_asis(command);
2991 WCMD_output_asis(equalW);
2992 WCMD_output_asis(newValue);
2993 WCMD_output_asis(newlineW);
3003 /****************************************************************************
3006 * Colors the terminal screen.
3009 void WCMD_color (void) {
3011 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3012 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3014 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3015 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3019 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3025 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3030 /* Convert the color hex digits */
3031 if (param1[0] == 0x00) {
3032 color = defaultColor;
3034 color = strtoulW(param1, NULL, 16);
3037 /* Fail if fg == bg color */
3038 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3043 /* Set the current screen contents and ensure all future writes
3044 remain this color */
3045 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3046 SetConsoleTextAttribute(hStdOut, color);