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, FALSE);
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, FALSE);
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 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
1088 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1090 CMD_LIST *thisCmdStart;
1093 /* Handle optional qualifiers (multiple are allowed) */
1094 while (*curPos && *curPos == '/') {
1095 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
1097 switch (toupperW(*curPos)) {
1098 case 'D': curPos++; expandDirs = TRUE; break;
1099 case 'L': curPos++; useNumbers = TRUE; break;
1101 /* Recursive is special case - /R can have an optional path following it */
1102 /* filenamesets are another special case - /F can have an optional options following it */
1106 BOOL isRecursive = (*curPos == 'R');
1111 /* Skip whitespace */
1113 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1115 /* Next parm is either qualifier, path/options or variable -
1116 only care about it if it is the path/options */
1117 if (*curPos && *curPos != '/' && *curPos != '%') {
1118 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
1120 static unsigned int once;
1121 if (!once++) WINE_FIXME("/F needs to handle options\n");
1127 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
1131 /* Skip whitespace between qualifiers */
1132 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1135 /* Skip whitespace before variable */
1136 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1138 /* Ensure line continues with variable */
1139 if (!*curPos || *curPos != '%') {
1140 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1144 /* Variable should follow */
1146 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
1147 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
1149 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1150 curPos = &curPos[i];
1152 /* Skip whitespace before IN */
1153 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1155 /* Ensure line continues with IN */
1157 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
1159 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1163 /* Save away where the set of data starts and the variable */
1164 thisDepth = (*cmdList)->bracketDepth;
1165 *cmdList = (*cmdList)->nextcommand;
1166 setStart = (*cmdList);
1168 /* Skip until the close bracket */
1169 WINE_TRACE("Searching %p as the set\n", *cmdList);
1171 (*cmdList)->command != NULL &&
1172 (*cmdList)->bracketDepth > thisDepth) {
1173 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1174 *cmdList = (*cmdList)->nextcommand;
1177 /* Skip the close bracket, if there is one */
1178 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1180 /* Syntax error if missing close bracket, or nothing following it
1181 and once we have the complete set, we expect a DO */
1182 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1183 if ((*cmdList == NULL)
1184 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1186 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1190 /* Save away the starting position for the commands (and offset for the
1192 cmdStart = *cmdList;
1194 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1198 /* Loop through all set entries */
1200 thisSet->command != NULL &&
1201 thisSet->bracketDepth >= thisDepth) {
1203 /* Loop through all entries on the same line */
1207 WINE_TRACE("Processing for set %p\n", thisSet);
1209 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL, TRUE))) {
1212 * If the parameter within the set has a wildcard then search for matching files
1213 * otherwise do a literal substitution.
1215 static const WCHAR wildcards[] = {'*','?','\0'};
1216 thisCmdStart = cmdStart;
1219 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1221 if (!useNumbers && !doFileset) {
1222 if (strpbrkW (item, wildcards)) {
1223 hff = FindFirstFileW(item, &fd);
1224 if (hff != INVALID_HANDLE_VALUE) {
1226 BOOL isDirectory = FALSE;
1228 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1230 /* Handle as files or dirs appropriately, but ignore . and .. */
1231 if (isDirectory == expandDirs &&
1232 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1233 (strcmpW(fd.cFileName, dotW) != 0))
1235 thisCmdStart = cmdStart;
1236 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1238 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1239 fd.cFileName, FALSE, TRUE);
1242 } while (FindNextFileW(hff, &fd) != 0);
1247 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1250 } else if (useNumbers) {
1251 /* Convert the first 3 numbers to signed longs and save */
1252 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1253 /* else ignore them! */
1255 /* Filesets - either a list of files, or a command to run and parse the output */
1256 } else if (doFileset && *itemStart != '"') {
1259 WCHAR temp_file[MAX_PATH];
1261 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1262 wine_dbgstr_w(item));
1264 /* If backquote or single quote, we need to launch that command
1265 and parse the results - use a temporary file */
1266 if (*itemStart == '`' || *itemStart == '\'') {
1268 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1269 static const WCHAR redirOut[] = {'>','%','s','\0'};
1270 static const WCHAR cmdW[] = {'C','M','D','\0'};
1272 /* Remove trailing character */
1273 itemStart[strlenW(itemStart)-1] = 0x00;
1275 /* Get temp filename */
1276 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1277 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1279 /* Execute program and redirect output */
1280 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1281 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1283 /* Open the file, read line by line and process */
1284 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1285 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1288 /* Open the file, read line by line and process */
1289 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1290 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1293 /* Process the input file */
1294 if (input == INVALID_HANDLE_VALUE) {
1295 WCMD_print_error ();
1296 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1298 return; /* FOR loop aborts at first failure here */
1302 WCHAR buffer[MAXSTRING] = {'\0'};
1303 WCHAR *where, *parm;
1305 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1307 /* Skip blank lines*/
1308 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
1309 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1310 wine_dbgstr_w(buffer));
1313 /* FIXME: The following should be moved into its own routine and
1314 reused for the string literal parsing below */
1315 thisCmdStart = cmdStart;
1317 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1318 cmdEnd = thisCmdStart;
1324 CloseHandle (input);
1327 /* Delete the temporary file */
1328 if (*itemStart == '`' || *itemStart == '\'') {
1329 DeleteFileW(temp_file);
1332 /* Filesets - A string literal */
1333 } else if (doFileset && *itemStart == '"') {
1334 WCHAR buffer[MAXSTRING] = {'\0'};
1335 WCHAR *where, *parm;
1337 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1338 strcpyW(buffer, item);
1339 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
1340 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1341 wine_dbgstr_w(buffer));
1344 /* FIXME: The following should be moved into its own routine and
1345 reused for the string literal parsing below */
1346 thisCmdStart = cmdStart;
1348 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1349 cmdEnd = thisCmdStart;
1353 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1354 cmdEnd = thisCmdStart;
1358 /* Move onto the next set line */
1359 thisSet = thisSet->nextcommand;
1362 /* If /L is provided, now run the for loop */
1365 static const WCHAR fmt[] = {'%','d','\0'};
1367 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1368 numbers[0], numbers[2], numbers[1]);
1370 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
1373 sprintfW(thisNum, fmt, i);
1374 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1376 thisCmdStart = cmdStart;
1378 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1380 cmdEnd = thisCmdStart;
1383 /* Now skip over the do part if we did not perform the for loop so far.
1384 We store in cmdEnd the next command after the do block, but we only
1385 know this if something was run. If it has not been, we need to calculate
1388 thisCmdStart = cmdStart;
1389 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
1390 WCMD_part_execute(&thisCmdStart, firstCmd, NULL, NULL, FALSE, FALSE);
1391 cmdEnd = thisCmdStart;
1394 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1395 all processing, OR it should be pointing to the end of && processing OR
1396 it should be pointing at the NULL end of bracket for the DO. The return
1397 value needs to be the NEXT command to execute, which it either is, or
1398 we need to step over the closing bracket */
1400 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1403 /**************************************************************************
1406 * Simple on-line help. Help text is stored in the resource file.
1409 void WCMD_give_help (const WCHAR *command)
1413 command = WCMD_skip_leading_spaces((WCHAR*) command);
1414 if (strlenW(command) == 0) {
1415 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1418 /* Display help message for builtin commands */
1419 for (i=0; i<=WCMD_EXIT; i++) {
1420 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1421 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1422 WCMD_output_asis (WCMD_LoadMessage(i));
1426 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1427 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1428 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1429 command, -1, externals[i], -1) == CSTR_EQUAL) {
1431 static const WCHAR helpW[] = {' ', '/','?','\0'};
1432 strcpyW(cmd, command);
1433 strcatW(cmd, helpW);
1434 WCMD_run_program(cmd, FALSE);
1438 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1443 /****************************************************************************
1446 * Batch file jump instruction. Not the most efficient algorithm ;-)
1447 * Prints error message if the specified label cannot be found - the file pointer is
1448 * then at EOF, effectively stopping the batch file.
1449 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1452 void WCMD_goto (CMD_LIST **cmdList) {
1454 WCHAR string[MAX_PATH];
1455 WCHAR current[MAX_PATH];
1457 /* Do not process any more parts of a processed multipart or multilines command */
1458 if (cmdList) *cmdList = NULL;
1460 if (context != NULL) {
1461 WCHAR *paramStart = param1, *str;
1462 static const WCHAR eofW[] = {':','e','o','f','\0'};
1464 if (param1[0] == 0x00) {
1465 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1469 /* Handle special :EOF label */
1470 if (lstrcmpiW (eofW, param1) == 0) {
1471 context -> skip_rest = TRUE;
1475 /* Support goto :label as well as goto label */
1476 if (*paramStart == ':') paramStart++;
1478 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1479 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1481 while (isspaceW (*str)) str++;
1485 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1488 /* ignore space at the end */
1490 if (lstrcmpiW (current, paramStart) == 0) return;
1493 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1498 /*****************************************************************************
1501 * Push a directory onto the stack
1504 void WCMD_pushd (const WCHAR *command)
1506 struct env_stack *curdir;
1508 static const WCHAR parmD[] = {'/','D','\0'};
1510 if (strchrW(command, '/') != NULL) {
1511 SetLastError(ERROR_INVALID_PARAMETER);
1516 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1517 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1518 if( !curdir || !thisdir ) {
1521 WINE_ERR ("out of memory\n");
1525 /* Change directory using CD code with /D parameter */
1526 strcpyW(quals, parmD);
1527 GetCurrentDirectoryW (1024, thisdir);
1529 WCMD_setshow_default(command);
1535 curdir -> next = pushd_directories;
1536 curdir -> strings = thisdir;
1537 if (pushd_directories == NULL) {
1538 curdir -> u.stackdepth = 1;
1540 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1542 pushd_directories = curdir;
1547 /*****************************************************************************
1550 * Pop a directory from the stack
1553 void WCMD_popd (void) {
1554 struct env_stack *temp = pushd_directories;
1556 if (!pushd_directories)
1559 /* pop the old environment from the stack, and make it the current dir */
1560 pushd_directories = temp->next;
1561 SetCurrentDirectoryW(temp->strings);
1562 LocalFree (temp->strings);
1566 /****************************************************************************
1569 * Batch file conditional.
1571 * On entry, cmdlist will point to command containing the IF, and optionally
1572 * the first command to execute (if brackets not found)
1573 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1574 * If ('s were found, execute all within that bracket
1575 * Command may optionally be followed by an ELSE - need to skip instructions
1576 * in the else using the same logic
1578 * FIXME: Much more syntax checking needed!
1581 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1583 int negate; /* Negate condition */
1584 int test; /* Condition evaluation result */
1585 WCHAR condition[MAX_PATH], *command, *s;
1586 static const WCHAR notW[] = {'n','o','t','\0'};
1587 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1588 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1589 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1590 static const WCHAR eqeqW[] = {'=','=','\0'};
1591 static const WCHAR parmI[] = {'/','I','\0'};
1592 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1594 negate = !lstrcmpiW(param1,notW);
1595 strcpyW(condition, (negate ? param2 : param1));
1596 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1598 if (!lstrcmpiW (condition, errlvlW)) {
1599 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL, FALSE);
1601 long int param_int = strtolW(param, &endptr, 10);
1603 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1606 test = ((long int)errorlevel >= param_int);
1607 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1609 else if (!lstrcmpiW (condition, existW)) {
1610 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE))
1611 != INVALID_FILE_ATTRIBUTES);
1612 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1614 else if (!lstrcmpiW (condition, defdW)) {
1615 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE),
1617 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1619 else if ((s = strstrW (p, eqeqW))) {
1620 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1621 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1623 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd, FALSE);
1624 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd, FALSE);
1625 test = caseInsensitive
1626 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1627 leftPart, leftPartEnd-leftPart+1,
1628 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1629 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1630 leftPart, leftPartEnd-leftPart+1,
1631 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1632 WCMD_parameter(s, 1, &command, NULL, FALSE);
1635 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1639 /* Process rest of IF statement which is on the same line
1640 Note: This may process all or some of the cmdList (eg a GOTO) */
1641 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1644 /****************************************************************************
1647 * Move a file, directory tree or wildcarded set of files.
1650 void WCMD_move (void)
1653 WIN32_FIND_DATAW fd;
1655 WCHAR input[MAX_PATH];
1656 WCHAR output[MAX_PATH];
1658 WCHAR dir[MAX_PATH];
1659 WCHAR fname[MAX_PATH];
1660 WCHAR ext[MAX_PATH];
1662 if (param1[0] == 0x00) {
1663 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1667 /* If no destination supplied, assume current directory */
1668 if (param2[0] == 0x00) {
1669 strcpyW(param2, dotW);
1672 /* If 2nd parm is directory, then use original filename */
1673 /* Convert partial path to full path */
1674 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1675 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1676 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1677 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1679 /* Split into components */
1680 WCMD_splitpath(input, drive, dir, fname, ext);
1682 hff = FindFirstFileW(input, &fd);
1683 if (hff == INVALID_HANDLE_VALUE)
1687 WCHAR dest[MAX_PATH];
1688 WCHAR src[MAX_PATH];
1692 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1694 /* Build src & dest name */
1695 strcpyW(src, drive);
1698 /* See if dest is an existing directory */
1699 attribs = GetFileAttributesW(output);
1700 if (attribs != INVALID_FILE_ATTRIBUTES &&
1701 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1702 strcpyW(dest, output);
1703 strcatW(dest, slashW);
1704 strcatW(dest, fd.cFileName);
1706 strcpyW(dest, output);
1709 strcatW(src, fd.cFileName);
1711 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1712 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1714 /* If destination exists, prompt unless /Y supplied */
1715 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1717 WCHAR copycmd[MAXSTRING];
1720 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1721 if (strstrW (quals, parmNoY))
1723 else if (strstrW (quals, parmY))
1726 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1727 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1728 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1729 && ! lstrcmpiW (copycmd, parmY));
1732 /* Prompt if overwriting */
1736 /* Ask for confirmation */
1737 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1738 ok = WCMD_ask_confirm(question, FALSE, NULL);
1739 LocalFree(question);
1741 /* So delete the destination prior to the move */
1743 if (!DeleteFileW(dest)) {
1744 WCMD_print_error ();
1753 status = MoveFileW(src, dest);
1755 status = 1; /* Anything other than 0 to prevent error msg below */
1759 WCMD_print_error ();
1762 } while (FindNextFileW(hff, &fd) != 0);
1767 /****************************************************************************
1770 * Suspend execution of a batch script until a key is typed
1773 void WCMD_pause (void)
1779 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1781 have_console = GetConsoleMode(hIn, &oldmode);
1783 SetConsoleMode(hIn, 0);
1785 WCMD_output_asis(anykey);
1786 WCMD_ReadFile(hIn, &key, 1, &count);
1788 SetConsoleMode(hIn, oldmode);
1791 /****************************************************************************
1794 * Delete a directory.
1797 void WCMD_remove_dir (WCHAR *command) {
1800 int argsProcessed = 0;
1801 WCHAR *argN = command;
1802 static const WCHAR parmS[] = {'/','S','\0'};
1803 static const WCHAR parmQ[] = {'/','Q','\0'};
1805 /* Loop through all args */
1807 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
1808 if (argN && argN[0] != '/') {
1809 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1810 wine_dbgstr_w(quals));
1813 /* If subdirectory search not supplied, just try to remove
1814 and report error if it fails (eg if it contains a file) */
1815 if (strstrW (quals, parmS) == NULL) {
1816 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1818 /* Otherwise use ShFileOp to recursively remove a directory */
1821 SHFILEOPSTRUCTW lpDir;
1824 if (strstrW (quals, parmQ) == NULL) {
1826 WCHAR question[MAXSTRING];
1827 static const WCHAR fmt[] = {'%','s',' ','\0'};
1829 /* Ask for confirmation */
1830 wsprintfW(question, fmt, thisArg);
1831 ok = WCMD_ask_confirm(question, TRUE, NULL);
1833 /* Abort if answer is 'N' */
1840 lpDir.pFrom = thisArg;
1841 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1842 lpDir.wFunc = FO_DELETE;
1844 /* SHFileOperationW needs file list with a double null termination */
1845 thisArg[lstrlenW(thisArg) + 1] = 0x00;
1847 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1852 /* Handle no valid args */
1853 if (argsProcessed == 0) {
1854 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1860 /****************************************************************************
1866 void WCMD_rename (void)
1870 WIN32_FIND_DATAW fd;
1871 WCHAR input[MAX_PATH];
1872 WCHAR *dotDst = NULL;
1874 WCHAR dir[MAX_PATH];
1875 WCHAR fname[MAX_PATH];
1876 WCHAR ext[MAX_PATH];
1880 /* Must be at least two args */
1881 if (param1[0] == 0x00 || param2[0] == 0x00) {
1882 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1887 /* Destination cannot contain a drive letter or directory separator */
1888 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
1889 SetLastError(ERROR_INVALID_PARAMETER);
1895 /* Convert partial path to full path */
1896 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1897 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1898 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1899 dotDst = strchrW(param2, '.');
1901 /* Split into components */
1902 WCMD_splitpath(input, drive, dir, fname, ext);
1904 hff = FindFirstFileW(input, &fd);
1905 if (hff == INVALID_HANDLE_VALUE)
1909 WCHAR dest[MAX_PATH];
1910 WCHAR src[MAX_PATH];
1911 WCHAR *dotSrc = NULL;
1914 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1916 /* FIXME: If dest name or extension is *, replace with filename/ext
1917 part otherwise use supplied name. This supports:
1919 ren jim.* fred.* etc
1920 However, windows has a more complex algorithm supporting eg
1921 ?'s and *'s mid name */
1922 dotSrc = strchrW(fd.cFileName, '.');
1924 /* Build src & dest name */
1925 strcpyW(src, drive);
1928 dirLen = strlenW(src);
1929 strcatW(src, fd.cFileName);
1932 if (param2[0] == '*') {
1933 strcatW(dest, fd.cFileName);
1934 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1936 strcatW(dest, param2);
1937 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1940 /* Build Extension */
1941 if (dotDst && (*(dotDst+1)=='*')) {
1942 if (dotSrc) strcatW(dest, dotSrc);
1943 } else if (dotDst) {
1944 if (dotDst) strcatW(dest, dotDst);
1947 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1948 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1950 status = MoveFileW(src, dest);
1953 WCMD_print_error ();
1956 } while (FindNextFileW(hff, &fd) != 0);
1961 /*****************************************************************************
1964 * Make a copy of the environment.
1966 static WCHAR *WCMD_dupenv( const WCHAR *env )
1976 len += (strlenW(&env[len]) + 1);
1978 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1981 WINE_ERR("out of memory\n");
1984 memcpy (env_copy, env, len*sizeof (WCHAR));
1990 /*****************************************************************************
1993 * setlocal pushes the environment onto a stack
1994 * Save the environment as unicode so we don't screw anything up.
1996 void WCMD_setlocal (const WCHAR *s) {
1998 struct env_stack *env_copy;
1999 WCHAR cwd[MAX_PATH];
2001 /* DISABLEEXTENSIONS ignored */
2003 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2006 WINE_ERR ("out of memory\n");
2010 env = GetEnvironmentStringsW ();
2012 env_copy->strings = WCMD_dupenv (env);
2013 if (env_copy->strings)
2015 env_copy->next = saved_environment;
2016 saved_environment = env_copy;
2018 /* Save the current drive letter */
2019 GetCurrentDirectoryW(MAX_PATH, cwd);
2020 env_copy->u.cwd = cwd[0];
2023 LocalFree (env_copy);
2025 FreeEnvironmentStringsW (env);
2029 /*****************************************************************************
2032 * endlocal pops the environment off a stack
2033 * Note: When searching for '=', search from WCHAR position 1, to handle
2034 * special internal environment variables =C:, =D: etc
2036 void WCMD_endlocal (void) {
2037 WCHAR *env, *old, *p;
2038 struct env_stack *temp;
2041 if (!saved_environment)
2044 /* pop the old environment from the stack */
2045 temp = saved_environment;
2046 saved_environment = temp->next;
2048 /* delete the current environment, totally */
2049 env = GetEnvironmentStringsW ();
2050 old = WCMD_dupenv (GetEnvironmentStringsW ());
2053 n = strlenW(&old[len]) + 1;
2054 p = strchrW(&old[len] + 1, '=');
2058 SetEnvironmentVariableW (&old[len], NULL);
2063 FreeEnvironmentStringsW (env);
2065 /* restore old environment */
2066 env = temp->strings;
2069 n = strlenW(&env[len]) + 1;
2070 p = strchrW(&env[len] + 1, '=');
2074 SetEnvironmentVariableW (&env[len], p);
2079 /* Restore current drive letter */
2080 if (IsCharAlphaW(temp->u.cwd)) {
2082 WCHAR cwd[MAX_PATH];
2083 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2085 wsprintfW(envvar, fmt, temp->u.cwd);
2086 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2087 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2088 SetCurrentDirectoryW(cwd);
2096 /*****************************************************************************
2097 * WCMD_setshow_default
2099 * Set/Show the current default directory
2102 void WCMD_setshow_default (const WCHAR *command) {
2108 WIN32_FIND_DATAW fd;
2110 static const WCHAR parmD[] = {'/','D','\0'};
2112 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2114 /* Skip /D and trailing whitespace if on the front of the command line */
2115 if (CompareStringW(LOCALE_USER_DEFAULT,
2116 NORM_IGNORECASE | SORT_STRINGSORT,
2117 command, 2, parmD, -1) == CSTR_EQUAL) {
2119 while (*command && (*command==' ' || *command=='\t'))
2123 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2124 if (strlenW(command) == 0) {
2125 strcatW (cwd, newlineW);
2126 WCMD_output_asis (cwd);
2129 /* Remove any double quotes, which may be in the
2130 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2133 if (*command != '"') *pos++ = *command;
2136 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2140 /* Search for appropriate directory */
2141 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2142 hff = FindFirstFileW(string, &fd);
2143 if (hff != INVALID_HANDLE_VALUE) {
2145 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2146 WCHAR fpath[MAX_PATH];
2148 WCHAR dir[MAX_PATH];
2149 WCHAR fname[MAX_PATH];
2150 WCHAR ext[MAX_PATH];
2151 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2153 /* Convert path into actual directory spec */
2154 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2155 WCMD_splitpath(fpath, drive, dir, fname, ext);
2158 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2161 } while (FindNextFileW(hff, &fd) != 0);
2165 /* Change to that directory */
2166 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2168 status = SetCurrentDirectoryW(string);
2171 WCMD_print_error ();
2175 /* Save away the actual new directory, to store as current location */
2176 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2178 /* Restore old directory if drive letter would change, and
2179 CD x:\directory /D (or pushd c:\directory) not supplied */
2180 if ((strstrW(quals, parmD) == NULL) &&
2181 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2182 SetCurrentDirectoryW(cwd);
2186 /* Set special =C: type environment variable, for drive letter of
2187 change of directory, even if path was restored due to missing
2188 /D (allows changing drive letter when not resident on that
2190 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2192 strcpyW(env, equalW);
2193 memcpy(env+1, string, 2 * sizeof(WCHAR));
2195 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2196 SetEnvironmentVariableW(env, string);
2203 /****************************************************************************
2206 * Set/Show the system date
2207 * FIXME: Can't change date yet
2210 void WCMD_setshow_date (void) {
2212 WCHAR curdate[64], buffer[64];
2214 static const WCHAR parmT[] = {'/','T','\0'};
2216 if (strlenW(param1) == 0) {
2217 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2218 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2219 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2220 if (strstrW (quals, parmT) == NULL) {
2221 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2222 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2224 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2228 else WCMD_print_error ();
2231 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2235 /****************************************************************************
2238 static int WCMD_compare( const void *a, const void *b )
2241 const WCHAR * const *str_a = a, * const *str_b = b;
2242 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2243 *str_a, -1, *str_b, -1 );
2244 if( r == CSTR_LESS_THAN ) return -1;
2245 if( r == CSTR_GREATER_THAN ) return 1;
2249 /****************************************************************************
2250 * WCMD_setshow_sortenv
2252 * sort variables into order for display
2253 * Optionally only display those who start with a stub
2254 * returns the count displayed
2256 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2258 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2261 if (stub) stublen = strlenW(stub);
2263 /* count the number of strings, and the total length */
2265 len += (strlenW(&s[len]) + 1);
2269 /* add the strings to an array */
2270 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2274 for( i=1; i<count; i++ )
2275 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2277 /* sort the array */
2278 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2281 for( i=0; i<count; i++ ) {
2282 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2283 NORM_IGNORECASE | SORT_STRINGSORT,
2284 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2285 /* Don't display special internal variables */
2286 if (str[i][0] != '=') {
2287 WCMD_output_asis(str[i]);
2288 WCMD_output_asis(newlineW);
2295 return displayedcount;
2298 /****************************************************************************
2301 * Set/Show the environment variables
2304 void WCMD_setshow_env (WCHAR *s) {
2309 static const WCHAR parmP[] = {'/','P','\0'};
2311 if (param1[0] == 0x00 && quals[0] == 0x00) {
2312 env = GetEnvironmentStringsW();
2313 WCMD_setshow_sortenv( env, NULL );
2317 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2318 if (CompareStringW(LOCALE_USER_DEFAULT,
2319 NORM_IGNORECASE | SORT_STRINGSORT,
2320 s, 2, parmP, -1) == CSTR_EQUAL) {
2321 WCHAR string[MAXSTRING];
2325 while (*s && (*s==' ' || *s=='\t')) s++;
2327 WCMD_strip_quotes(s);
2329 /* If no parameter, or no '=' sign, return an error */
2330 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2331 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2335 /* Output the prompt */
2337 if (strlenW(p) != 0) WCMD_output_asis(p);
2339 /* Read the reply */
2340 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2342 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2343 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2344 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2345 wine_dbgstr_w(string));
2346 status = SetEnvironmentVariableW(s, string);
2353 WCMD_strip_quotes(s);
2354 p = strchrW (s, '=');
2356 env = GetEnvironmentStringsW();
2357 if (WCMD_setshow_sortenv( env, s ) == 0) {
2358 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2365 if (strlenW(p) == 0) p = NULL;
2366 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2368 status = SetEnvironmentVariableW(s, p);
2369 gle = GetLastError();
2370 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2372 } else if ((!status)) WCMD_print_error();
2373 else errorlevel = 0;
2377 /****************************************************************************
2380 * Set/Show the path environment variable
2383 void WCMD_setshow_path (const WCHAR *command) {
2387 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2388 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2390 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
2391 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2393 WCMD_output_asis ( pathEqW);
2394 WCMD_output_asis ( string);
2395 WCMD_output_asis ( newlineW);
2398 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2402 if (*command == '=') command++; /* Skip leading '=' */
2403 status = SetEnvironmentVariableW(pathW, command);
2404 if (!status) WCMD_print_error();
2408 /****************************************************************************
2409 * WCMD_setshow_prompt
2411 * Set or show the command prompt.
2414 void WCMD_setshow_prompt (void) {
2417 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2419 if (strlenW(param1) == 0) {
2420 SetEnvironmentVariableW(promptW, NULL);
2424 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2425 if (strlenW(s) == 0) {
2426 SetEnvironmentVariableW(promptW, NULL);
2428 else SetEnvironmentVariableW(promptW, s);
2432 /****************************************************************************
2435 * Set/Show the system time
2436 * FIXME: Can't change time yet
2439 void WCMD_setshow_time (void) {
2441 WCHAR curtime[64], buffer[64];
2444 static const WCHAR parmT[] = {'/','T','\0'};
2446 if (strlenW(param1) == 0) {
2448 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2449 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2450 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2451 if (strstrW (quals, parmT) == NULL) {
2452 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2453 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2455 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2459 else WCMD_print_error ();
2462 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2466 /****************************************************************************
2469 * Shift batch parameters.
2470 * Optional /n says where to start shifting (n=0-8)
2473 void WCMD_shift (const WCHAR *command) {
2476 if (context != NULL) {
2477 WCHAR *pos = strchrW(command, '/');
2482 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2483 start = (*(pos+1) - '0');
2485 SetLastError(ERROR_INVALID_PARAMETER);
2490 WINE_TRACE("Shifting variables, starting at %d\n", start);
2491 for (i=start;i<=8;i++) {
2492 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2494 context -> shift_count[9] = context -> shift_count[9] + 1;
2499 /****************************************************************************
2502 void WCMD_start(const WCHAR *command)
2504 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
2505 '\\','s','t','a','r','t','.','e','x','e',0};
2506 WCHAR file[MAX_PATH];
2509 PROCESS_INFORMATION pi;
2511 GetWindowsDirectoryW( file, MAX_PATH );
2512 strcatW( file, exeW );
2513 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
2514 strcpyW( cmdline, file );
2515 strcatW( cmdline, spaceW );
2516 strcatW( cmdline, command );
2518 memset( &st, 0, sizeof(STARTUPINFOW) );
2519 st.cb = sizeof(STARTUPINFOW);
2521 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
2523 WaitForSingleObject( pi.hProcess, INFINITE );
2524 GetExitCodeProcess( pi.hProcess, &errorlevel );
2525 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
2526 CloseHandle(pi.hProcess);
2527 CloseHandle(pi.hThread);
2531 SetLastError(ERROR_FILE_NOT_FOUND);
2532 WCMD_print_error ();
2535 HeapFree( GetProcessHeap(), 0, cmdline );
2538 /****************************************************************************
2541 * Set the console title
2543 void WCMD_title (const WCHAR *command) {
2544 SetConsoleTitleW(command);
2547 /****************************************************************************
2550 * Copy a file to standard output.
2553 void WCMD_type (WCHAR *command) {
2556 WCHAR *argN = command;
2557 BOOL writeHeaders = FALSE;
2559 if (param1[0] == 0x00) {
2560 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2564 if (param2[0] != 0x00) writeHeaders = TRUE;
2566 /* Loop through all args */
2569 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
2577 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2578 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2579 FILE_ATTRIBUTE_NORMAL, NULL);
2580 if (h == INVALID_HANDLE_VALUE) {
2581 WCMD_print_error ();
2582 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2586 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2587 WCMD_output(fmt, thisArg);
2589 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2590 if (count == 0) break; /* ReadFile reports success on EOF! */
2592 WCMD_output_asis (buffer);
2599 /****************************************************************************
2602 * Output either a file or stdin to screen in pages
2605 void WCMD_more (WCHAR *command) {
2608 WCHAR *argN = command;
2610 WCHAR moreStrPage[100];
2613 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2614 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2615 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2616 ')',' ','-','-','\n','\0'};
2617 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2619 /* Prefix the NLS more with '-- ', then load the text */
2621 strcpyW(moreStr, moreStart);
2622 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2623 (sizeof(moreStr)/sizeof(WCHAR))-3);
2625 if (param1[0] == 0x00) {
2627 /* Wine implements pipes via temporary files, and hence stdin is
2628 effectively reading from the file. This means the prompts for
2629 more are satisfied by the next line from the input (file). To
2630 avoid this, ensure stdin is to the console */
2631 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2632 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2633 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2634 FILE_ATTRIBUTE_NORMAL, 0);
2635 WINE_TRACE("No parms - working probably in pipe mode\n");
2636 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2638 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2639 once you get in this bit unless due to a pipe, its going to end badly... */
2640 wsprintfW(moreStrPage, moreFmt, moreStr);
2642 WCMD_enter_paged_mode(moreStrPage);
2643 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2644 if (count == 0) break; /* ReadFile reports success on EOF! */
2646 WCMD_output_asis (buffer);
2648 WCMD_leave_paged_mode();
2650 /* Restore stdin to what it was */
2651 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2652 CloseHandle(hConIn);
2656 BOOL needsPause = FALSE;
2658 /* Loop through all args */
2659 WINE_TRACE("Parms supplied - working through each file\n");
2660 WCMD_enter_paged_mode(moreStrPage);
2663 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
2671 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2672 WCMD_leave_paged_mode();
2673 WCMD_output_asis(moreStrPage);
2674 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2675 WCMD_enter_paged_mode(moreStrPage);
2679 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2680 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2681 FILE_ATTRIBUTE_NORMAL, NULL);
2682 if (h == INVALID_HANDLE_VALUE) {
2683 WCMD_print_error ();
2684 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2688 ULONG64 fileLen = 0;
2689 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2691 /* Get the file size */
2692 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2693 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2696 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2697 if (count == 0) break; /* ReadFile reports success on EOF! */
2701 /* Update % count (would be used in WCMD_output_asis as prompt) */
2702 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2704 WCMD_output_asis (buffer);
2710 WCMD_leave_paged_mode();
2714 /****************************************************************************
2717 * Display verify flag.
2718 * FIXME: We don't actually do anything with the verify flag other than toggle
2722 void WCMD_verify (const WCHAR *command) {
2726 count = strlenW(command);
2728 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2729 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2732 if (lstrcmpiW(command, onW) == 0) {
2736 else if (lstrcmpiW(command, offW) == 0) {
2737 verify_mode = FALSE;
2740 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2743 /****************************************************************************
2746 * Display version info.
2749 void WCMD_version (void) {
2751 WCMD_output_asis (version_string);
2755 /****************************************************************************
2758 * Display volume information (set_label = FALSE)
2759 * Additionally set volume label (set_label = TRUE)
2760 * Returns 1 on success, 0 otherwise
2763 int WCMD_volume(BOOL set_label, const WCHAR *path)
2765 DWORD count, serial;
2766 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2769 if (strlenW(path) == 0) {
2770 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2772 WCMD_print_error ();
2775 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2776 &serial, NULL, NULL, NULL, 0);
2779 static const WCHAR fmt[] = {'%','s','\\','\0'};
2780 if ((path[1] != ':') || (strlenW(path) != 2)) {
2781 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2784 wsprintfW (curdir, fmt, path);
2785 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2790 WCMD_print_error ();
2793 if (label[0] != '\0') {
2794 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
2798 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
2801 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
2802 HIWORD(serial), LOWORD(serial));
2804 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2805 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2807 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2808 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2810 if (strlenW(path) != 0) {
2811 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2814 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2820 /**************************************************************************
2823 * Exit either the process, or just this batch program
2827 void WCMD_exit (CMD_LIST **cmdList) {
2829 static const WCHAR parmB[] = {'/','B','\0'};
2830 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2832 if (context && lstrcmpiW(quals, parmB) == 0) {
2834 context -> skip_rest = TRUE;
2842 /*****************************************************************************
2845 * Lists or sets file associations (assoc = TRUE)
2846 * Lists or sets file types (assoc = FALSE)
2848 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2851 DWORD accessOptions = KEY_READ;
2853 LONG rc = ERROR_SUCCESS;
2854 WCHAR keyValue[MAXSTRING];
2855 DWORD valueLen = MAXSTRING;
2857 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2858 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2860 /* See if parameter includes '=' */
2862 newValue = strchrW(command, '=');
2863 if (newValue) accessOptions |= KEY_WRITE;
2865 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2866 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2867 accessOptions, &key) != ERROR_SUCCESS) {
2868 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2872 /* If no parameters then list all associations */
2873 if (*command == 0x00) {
2876 /* Enumerate all the keys */
2877 while (rc != ERROR_NO_MORE_ITEMS) {
2878 WCHAR keyName[MAXSTRING];
2881 /* Find the next value */
2882 nameLen = MAXSTRING;
2883 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2885 if (rc == ERROR_SUCCESS) {
2887 /* Only interested in extension ones if assoc, or others
2889 if ((keyName[0] == '.' && assoc) ||
2890 (!(keyName[0] == '.') && (!assoc)))
2892 WCHAR subkey[MAXSTRING];
2893 strcpyW(subkey, keyName);
2894 if (!assoc) strcatW(subkey, shOpCmdW);
2896 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2898 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2899 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2900 WCMD_output_asis(keyName);
2901 WCMD_output_asis(equalW);
2902 /* If no default value found, leave line empty after '=' */
2903 if (rc == ERROR_SUCCESS) {
2904 WCMD_output_asis(keyValue);
2906 WCMD_output_asis(newlineW);
2907 RegCloseKey(readKey);
2915 /* Parameter supplied - if no '=' on command line, its a query */
2916 if (newValue == NULL) {
2918 WCHAR subkey[MAXSTRING];
2920 /* Query terminates the parameter at the first space */
2921 strcpyW(keyValue, command);
2922 space = strchrW(keyValue, ' ');
2923 if (space) *space=0x00;
2925 /* Set up key name */
2926 strcpyW(subkey, keyValue);
2927 if (!assoc) strcatW(subkey, shOpCmdW);
2929 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2931 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2932 WCMD_output_asis(command);
2933 WCMD_output_asis(equalW);
2934 /* If no default value found, leave line empty after '=' */
2935 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2936 WCMD_output_asis(newlineW);
2937 RegCloseKey(readKey);
2940 WCHAR msgbuffer[MAXSTRING];
2942 /* Load the translated 'File association not found' */
2944 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2946 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2948 WCMD_output_stderr(msgbuffer, keyValue);
2952 /* Not a query - its a set or clear of a value */
2955 WCHAR subkey[MAXSTRING];
2957 /* Get pointer to new value */
2961 /* Set up key name */
2962 strcpyW(subkey, command);
2963 if (!assoc) strcatW(subkey, shOpCmdW);
2965 /* If nothing after '=' then clear value - only valid for ASSOC */
2966 if (*newValue == 0x00) {
2968 if (assoc) rc = RegDeleteKeyW(key, command);
2969 if (assoc && rc == ERROR_SUCCESS) {
2970 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2972 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2977 WCHAR msgbuffer[MAXSTRING];
2979 /* Load the translated 'File association not found' */
2981 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2982 sizeof(msgbuffer)/sizeof(WCHAR));
2984 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2985 sizeof(msgbuffer)/sizeof(WCHAR));
2987 WCMD_output_stderr(msgbuffer, keyValue);
2991 /* It really is a set value = contents */
2993 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2994 accessOptions, NULL, &readKey, NULL);
2995 if (rc == ERROR_SUCCESS) {
2996 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2998 sizeof(WCHAR) * (strlenW(newValue) + 1));
2999 RegCloseKey(readKey);
3002 if (rc != ERROR_SUCCESS) {
3006 WCMD_output_asis(command);
3007 WCMD_output_asis(equalW);
3008 WCMD_output_asis(newValue);
3009 WCMD_output_asis(newlineW);
3019 /****************************************************************************
3022 * Colors the terminal screen.
3025 void WCMD_color (void) {
3027 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3028 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3030 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3031 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3035 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3041 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3046 /* Convert the color hex digits */
3047 if (param1[0] == 0x00) {
3048 color = defaultColor;
3050 color = strtoulW(param1, NULL, 16);
3053 /* Fail if fg == bg color */
3054 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3059 /* Set the current screen contents and ensure all future writes
3060 remain this color */
3061 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3062 SetConsoleTextAttribute(hStdOut, color);