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 {'T','I','M','E','\0'},
78 {'T','I','T','L','E','\0'},
79 {'T','Y','P','E','\0'},
80 {'V','E','R','I','F','Y','\0'},
83 {'E','N','D','L','O','C','A','L','\0'},
84 {'S','E','T','L','O','C','A','L','\0'},
85 {'P','U','S','H','D','\0'},
86 {'P','O','P','D','\0'},
87 {'A','S','S','O','C','\0'},
88 {'C','O','L','O','R','\0'},
89 {'F','T','Y','P','E','\0'},
90 {'M','O','R','E','\0'},
91 {'C','H','O','I','C','E','\0'},
92 {'E','X','I','T','\0'}
94 static const WCHAR externals[][10] = {
95 {'A','T','T','R','I','B','\0'},
96 {'X','C','O','P','Y','\0'}
98 static const WCHAR fslashW[] = {'/','\0'};
99 static const WCHAR onW[] = {'O','N','\0'};
100 static const WCHAR offW[] = {'O','F','F','\0'};
101 static const WCHAR parmY[] = {'/','Y','\0'};
102 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
104 static HINSTANCE hinst;
105 static struct env_stack *saved_environment;
106 static BOOL verify_mode = FALSE;
108 /**************************************************************************
111 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
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,
120 const BOOL *optionAll) {
122 WCHAR msgbuffer[MAXSTRING];
123 WCHAR Ybuffer[MAXSTRING];
124 WCHAR Nbuffer[MAXSTRING];
125 WCHAR Abuffer[MAXSTRING];
126 WCHAR answer[MAX_PATH] = {'\0'};
129 /* Load the translated 'Are you sure', plus valid answers */
130 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
131 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
132 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
133 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
135 /* Loop waiting on a Y or N */
136 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
137 static const WCHAR startBkt[] = {' ','(','\0'};
138 static const WCHAR endBkt[] = {')','?','\0'};
140 WCMD_output_asis (message);
142 WCMD_output_asis (msgbuffer);
144 WCMD_output_asis (startBkt);
145 WCMD_output_asis (Ybuffer);
146 WCMD_output_asis (fslashW);
147 WCMD_output_asis (Nbuffer);
149 WCMD_output_asis (fslashW);
150 WCMD_output_asis (Abuffer);
152 WCMD_output_asis (endBkt);
153 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
154 answer[0] = toupperW(answer[0]);
157 /* Return the answer */
158 return ((answer[0] == Ybuffer[0]) ||
159 (optionAll && (answer[0] == Abuffer[0])));
162 /****************************************************************************
165 * Clear the terminal screen.
168 void WCMD_clear_screen (void) {
170 /* Emulate by filling the screen from the top left to bottom right with
171 spaces, then moving the cursor to the top left afterwards */
172 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
173 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
175 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
180 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
184 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
185 SetConsoleCursorPosition(hStdOut, topLeft);
189 /****************************************************************************
192 * Change the default i/o device (ie redirect STDin/STDout).
195 void WCMD_change_tty (void) {
197 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
201 /****************************************************************************
206 void WCMD_choice (const WCHAR * command) {
208 static const WCHAR bellW[] = {7,0};
209 static const WCHAR commaW[] = {',',0};
210 static const WCHAR bracket_open[] = {'[',0};
211 static const WCHAR bracket_close[] = {']','?',0};
216 WCHAR *my_command = NULL;
217 WCHAR opt_default = 0;
218 DWORD opt_timeout = 0;
225 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
228 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
232 ptr = WCMD_skip_leading_spaces(my_command);
233 while (*ptr == '/') {
234 switch (toupperW(ptr[1])) {
237 /* the colon is optional */
241 if (!*ptr || isspaceW(*ptr)) {
242 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
243 HeapFree(GetProcessHeap(), 0, my_command);
247 /* remember the allowed keys (overwrite previous /C option) */
249 while (*ptr && (!isspaceW(*ptr)))
253 /* terminate allowed chars */
255 ptr = WCMD_skip_leading_spaces(&ptr[1]);
257 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
262 ptr = WCMD_skip_leading_spaces(&ptr[2]);
267 ptr = WCMD_skip_leading_spaces(&ptr[2]);
272 /* the colon is optional */
276 opt_default = *ptr++;
278 if (!opt_default || (*ptr != ',')) {
279 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
280 HeapFree(GetProcessHeap(), 0, my_command);
286 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
292 opt_timeout = atoiW(answer);
294 ptr = WCMD_skip_leading_spaces(ptr);
298 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
299 HeapFree(GetProcessHeap(), 0, my_command);
305 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
308 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
310 /* use default keys, when needed: localized versions of "Y"es and "No" */
312 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
313 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
318 /* print the question, when needed */
320 WCMD_output_asis(ptr);
324 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
328 /* print a list of all allowed answers inside brackets */
329 WCMD_output_asis(bracket_open);
332 while ((answer[0] = *ptr++)) {
333 WCMD_output_asis(answer);
335 WCMD_output_asis(commaW);
337 WCMD_output_asis(bracket_close);
342 /* FIXME: Add support for option /T */
343 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
346 answer[0] = toupperW(answer[0]);
348 ptr = strchrW(opt_c, answer[0]);
350 WCMD_output_asis(answer);
351 WCMD_output_asis(newline);
353 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
355 errorlevel = (ptr - opt_c) + 1;
356 WINE_TRACE("answer: %d\n", errorlevel);
357 HeapFree(GetProcessHeap(), 0, my_command);
362 /* key not allowed: play the bell */
363 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
364 WCMD_output_asis(bellW);
369 /****************************************************************************
372 * Copy a file or wildcarded set.
373 * FIXME: Add support for a+b+c type syntax
376 void WCMD_copy (void) {
381 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
383 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
384 BOOL copyToDir = FALSE;
385 WCHAR srcspec[MAX_PATH];
389 WCHAR fname[MAX_PATH];
392 if (param1[0] == 0x00) {
393 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
397 /* Convert source into full spec */
398 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
399 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
400 if (srcpath[strlenW(srcpath) - 1] == '\\')
401 srcpath[strlenW(srcpath) - 1] = '\0';
403 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
404 attribs = GetFileAttributesW(srcpath);
408 strcpyW(srcspec, srcpath);
410 /* If a directory, then add \* on the end when searching */
411 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
412 strcatW(srcpath, slashW);
413 strcatW(srcspec, slashW);
414 strcatW(srcspec, starW);
416 WCMD_splitpath(srcpath, drive, dir, fname, ext);
417 strcpyW(srcpath, drive);
418 strcatW(srcpath, dir);
421 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
423 /* If no destination supplied, assume current directory */
424 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
425 if (param2[0] == 0x00) {
426 strcpyW(param2, dotW);
429 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
430 if (outpath[strlenW(outpath) - 1] == '\\')
431 outpath[strlenW(outpath) - 1] = '\0';
432 attribs = GetFileAttributesW(outpath);
433 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
434 strcatW (outpath, slashW);
437 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
438 wine_dbgstr_w(outpath), copyToDir);
440 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
441 if (strstrW (quals, parmNoY))
443 else if (strstrW (quals, parmY))
446 /* By default, we will force the overwrite in batch mode and ask for
447 * confirmation in interactive mode. */
450 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
451 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
452 * default behavior. */
453 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
454 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
455 if (!lstrcmpiW (copycmd, parmY))
457 else if (!lstrcmpiW (copycmd, parmNoY))
462 /* Loop through all source files */
463 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
464 hff = FindFirstFileW(srcspec, &fd);
465 if (hff != INVALID_HANDLE_VALUE) {
467 WCHAR outname[MAX_PATH];
468 WCHAR srcname[MAX_PATH];
469 BOOL overwrite = force;
471 /* Destination is either supplied filename, or source name in
472 supplied destination directory */
473 strcpyW(outname, outpath);
474 if (copyToDir) strcatW(outname, fd.cFileName);
475 strcpyW(srcname, srcpath);
476 strcatW(srcname, fd.cFileName);
478 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
479 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
481 /* Skip . and .., and directories */
482 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
484 WINE_TRACE("Skipping directories\n");
487 /* Prompt before overwriting */
488 else if (!overwrite) {
489 attribs = GetFileAttributesW(outname);
490 if (attribs != INVALID_FILE_ATTRIBUTES) {
491 WCHAR buffer[MAXSTRING];
492 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
493 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
495 else overwrite = TRUE;
498 /* Do the copy as appropriate */
500 status = CopyFileW(srcname, outname, FALSE);
501 if (!status) WCMD_print_error ();
504 } while (FindNextFileW(hff, &fd) != 0);
511 /****************************************************************************
514 * Create a directory (and, if needed, any intermediate directories).
516 * Modifies its argument by replacing slashes temporarily with nulls.
519 static BOOL create_full_path(WCHAR* path)
523 /* don't mess with drive letter portion of path, if any */
528 /* Strip trailing slashes. */
529 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
532 /* Step through path, creating intermediate directories as needed. */
533 /* First component includes drive letter, if any. */
537 /* Skip to end of component */
538 while (*p == '\\') p++;
539 while (*p && *p != '\\') p++;
541 /* path is now the original full path */
542 return CreateDirectoryW(path, NULL);
544 /* Truncate path, create intermediate directory, and restore path */
546 rv = CreateDirectoryW(path, NULL);
548 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
555 void WCMD_create_dir (WCHAR *command) {
557 WCHAR *argN = command;
559 if (param1[0] == 0x00) {
560 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
563 /* Loop through all args */
565 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL);
567 if (!create_full_path(thisArg)) {
574 /* Parse the /A options given by the user on the commandline
575 * into a bitmask of wanted attributes (*wantSet),
576 * and a bitmask of unwanted attributes (*wantClear).
578 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
579 static const WCHAR parmA[] = {'/','A','\0'};
582 /* both are strictly 'out' parameters */
586 /* For each /A argument */
587 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
591 /* Skip optional : */
594 /* For each of the attribute specifier chars to this /A option */
595 for (; *p != 0 && *p != '/'; p++) {
604 /* Convert the attribute specifier to a bit in one of the masks */
606 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
607 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
608 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
609 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
611 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
621 /* If filename part of parameter is * or *.*,
622 * and neither /Q nor /P options were given,
623 * prompt the user whether to proceed.
624 * Returns FALSE if user says no, TRUE otherwise.
625 * *pPrompted is set to TRUE if the user is prompted.
626 * (If /P supplied, del will prompt for individual files later.)
628 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
629 static const WCHAR parmP[] = {'/','P','\0'};
630 static const WCHAR parmQ[] = {'/','Q','\0'};
632 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
633 static const WCHAR anyExt[]= {'.','*','\0'};
636 WCHAR fname[MAX_PATH];
638 WCHAR fpath[MAX_PATH];
640 /* Convert path into actual directory spec */
641 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
642 WCMD_splitpath(fpath, drive, dir, fname, ext);
644 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
645 if ((strcmpW(fname, starW) == 0) &&
646 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
648 WCHAR question[MAXSTRING];
649 static const WCHAR fmt[] = {'%','s',' ','\0'};
651 /* Caller uses this to suppress "file not found" warning later */
654 /* Ask for confirmation */
655 wsprintfW(question, fmt, fpath);
656 return WCMD_ask_confirm(question, TRUE, NULL);
659 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
663 /* Helper function for WCMD_delete().
664 * Deletes a single file, directory, or wildcard.
665 * If /S was given, does it recursively.
666 * Returns TRUE if a file was deleted.
668 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
670 static const WCHAR parmP[] = {'/','P','\0'};
671 static const WCHAR parmS[] = {'/','S','\0'};
672 static const WCHAR parmF[] = {'/','F','\0'};
674 DWORD unwanted_attrs;
676 WCHAR argCopy[MAX_PATH];
679 WCHAR fpath[MAX_PATH];
681 BOOL handleParm = TRUE;
683 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
685 strcpyW(argCopy, thisArg);
686 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
687 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
689 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
690 /* Skip this arg if user declines to delete *.* */
694 /* First, try to delete in the current directory */
695 hff = FindFirstFileW(argCopy, &fd);
696 if (hff == INVALID_HANDLE_VALUE) {
702 /* Support del <dirname> by just deleting all files dirname\* */
704 && (strchrW(argCopy,'*') == NULL)
705 && (strchrW(argCopy,'?') == NULL)
706 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
708 WCHAR modifiedParm[MAX_PATH];
709 static const WCHAR slashStar[] = {'\\','*','\0'};
711 strcpyW(modifiedParm, argCopy);
712 strcatW(modifiedParm, slashStar);
715 WCMD_delete_one(modifiedParm);
717 } else if (handleParm) {
719 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
720 strcpyW (fpath, argCopy);
722 p = strrchrW (fpath, '\\');
725 strcatW (fpath, fd.cFileName);
727 else strcpyW (fpath, fd.cFileName);
728 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
731 /* Handle attribute matching (/A) */
732 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
733 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
735 /* /P means prompt for each file */
736 if (ok && strstrW (quals, parmP) != NULL) {
737 WCHAR question[MAXSTRING];
739 /* Ask for confirmation */
740 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
741 ok = WCMD_ask_confirm(question, FALSE, NULL);
744 /* Only proceed if ok to */
747 /* If file is read only, and /A:r or /F supplied, delete it */
748 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
749 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
750 strstrW (quals, parmF) != NULL)) {
751 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
754 /* Now do the delete */
755 if (!DeleteFileW(fpath)) WCMD_print_error ();
759 } while (FindNextFileW(hff, &fd) != 0);
763 /* Now recurse into all subdirectories handling the parameter in the same way */
764 if (strstrW (quals, parmS) != NULL) {
766 WCHAR thisDir[MAX_PATH];
771 WCHAR fname[MAX_PATH];
774 /* Convert path into actual directory spec */
775 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
776 WCMD_splitpath(thisDir, drive, dir, fname, ext);
778 strcpyW(thisDir, drive);
779 strcatW(thisDir, dir);
780 cPos = strlenW(thisDir);
782 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
784 /* Append '*' to the directory */
786 thisDir[cPos+1] = 0x00;
788 hff = FindFirstFileW(thisDir, &fd);
790 /* Remove residual '*' */
791 thisDir[cPos] = 0x00;
793 if (hff != INVALID_HANDLE_VALUE) {
794 DIRECTORY_STACK *allDirs = NULL;
795 DIRECTORY_STACK *lastEntry = NULL;
798 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
799 (strcmpW(fd.cFileName, dotdotW) != 0) &&
800 (strcmpW(fd.cFileName, dotW) != 0)) {
802 DIRECTORY_STACK *nextDir;
803 WCHAR subParm[MAX_PATH];
805 /* Work out search parameter in sub dir */
806 strcpyW (subParm, thisDir);
807 strcatW (subParm, fd.cFileName);
808 strcatW (subParm, slashW);
809 strcatW (subParm, fname);
810 strcatW (subParm, ext);
811 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
813 /* Allocate memory, add to list */
814 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
815 if (allDirs == NULL) allDirs = nextDir;
816 if (lastEntry != NULL) lastEntry->next = nextDir;
818 nextDir->next = NULL;
819 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
820 (strlenW(subParm)+1) * sizeof(WCHAR));
821 strcpyW(nextDir->dirName, subParm);
823 } while (FindNextFileW(hff, &fd) != 0);
826 /* Go through each subdir doing the delete */
827 while (allDirs != NULL) {
828 DIRECTORY_STACK *tempDir;
830 tempDir = allDirs->next;
831 found |= WCMD_delete_one (allDirs->dirName);
833 HeapFree(GetProcessHeap(),0,allDirs->dirName);
834 HeapFree(GetProcessHeap(),0,allDirs);
843 /****************************************************************************
846 * Delete a file or wildcarded set.
849 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
850 * - Each set is a pattern, eg /ahr /as-r means
851 * readonly+hidden OR nonreadonly system files
852 * - The '-' applies to a single field, ie /a:-hr means read only
856 BOOL WCMD_delete (WCHAR *command) {
859 BOOL argsProcessed = FALSE;
860 BOOL foundAny = FALSE;
864 for (argno=0; ; argno++) {
869 thisArg = WCMD_parameter (command, argno, &argN, NULL);
871 break; /* no more parameters */
873 continue; /* skip options */
875 argsProcessed = TRUE;
876 found = WCMD_delete_one(thisArg);
879 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
884 /* Handle no valid args */
886 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
894 * Returns a trimmed version of s with all leading and trailing whitespace removed
898 static WCHAR *WCMD_strtrim(const WCHAR *s)
900 DWORD len = strlenW(s);
901 const WCHAR *start = s;
904 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
907 while (isspaceW(*start)) start++;
909 const WCHAR *end = s + len - 1;
910 while (end > start && isspaceW(*end)) end--;
911 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
912 result[end - start + 1] = '\0';
920 /****************************************************************************
923 * Echo input to the screen (or not). We don't try to emulate the bugs
924 * in DOS (try typing "ECHO ON AGAIN" for an example).
927 void WCMD_echo (const WCHAR *command)
930 const WCHAR *origcommand = command;
933 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
934 || command[0]==':' || command[0]==';')
937 trimmed = WCMD_strtrim(command);
938 if (!trimmed) return;
940 count = strlenW(trimmed);
941 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
942 && origcommand[0]!=';') {
943 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
944 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
948 if (lstrcmpiW(trimmed, onW) == 0)
950 else if (lstrcmpiW(trimmed, offW) == 0)
953 WCMD_output_asis (command);
954 WCMD_output_asis (newline);
956 HeapFree(GetProcessHeap(), 0, trimmed);
959 /*****************************************************************************
962 * Execute a command, and any && or bracketed follow on to the command. The
963 * first command to be executed may not be at the front of the
964 * commands->thiscommand string (eg. it may point after a DO or ELSE)
966 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
967 const WCHAR *variable, const WCHAR *value,
968 BOOL isIF, BOOL conditionTRUE)
970 CMD_LIST *curPosition = *cmdList;
971 int myDepth = (*cmdList)->bracketDepth;
973 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
974 cmdList, wine_dbgstr_w(firstcmd),
975 wine_dbgstr_w(variable), wine_dbgstr_w(value),
978 /* Skip leading whitespace between condition and the command */
979 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
981 /* Process the first command, if there is one */
982 if (conditionTRUE && firstcmd && *firstcmd) {
983 WCHAR *command = WCMD_strdupW(firstcmd);
984 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
985 HeapFree(GetProcessHeap(), 0, command);
989 /* If it didn't move the position, step to next command */
990 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
992 /* Process any other parts of the command */
994 BOOL processThese = TRUE;
996 if (isIF) processThese = conditionTRUE;
999 static const WCHAR ifElse[] = {'e','l','s','e'};
1001 /* execute all appropriate commands */
1002 curPosition = *cmdList;
1004 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1006 (*cmdList)->prevDelim,
1007 (*cmdList)->bracketDepth, myDepth);
1009 /* Execute any statements appended to the line */
1010 /* FIXME: Only if previous call worked for && or failed for || */
1011 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1012 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1013 if (processThese && (*cmdList)->command) {
1014 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1017 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1019 /* Execute any appended to the statement with (...) */
1020 } else if ((*cmdList)->bracketDepth > myDepth) {
1022 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1023 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1025 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1027 /* End of the command - does 'ELSE ' follow as the next command? */
1030 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1031 (*cmdList)->command)) {
1033 /* Swap between if and else processing */
1034 processThese = !processThese;
1036 /* Process the ELSE part */
1038 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1039 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1041 /* Skip leading whitespace between condition and the command */
1042 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1044 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1047 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1049 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1058 /**************************************************************************
1061 * Batch file loop processing.
1063 * On entry: cmdList contains the syntax up to the set
1064 * next cmdList and all in that bracket contain the set data
1065 * next cmdlist contains the DO cmd
1066 * following that is either brackets or && entries (as per if)
1070 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1072 WIN32_FIND_DATAW fd;
1075 static const WCHAR inW[] = {'i','n'};
1076 static const WCHAR doW[] = {'d','o'};
1077 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1083 BOOL expandDirs = FALSE;
1084 BOOL useNumbers = FALSE;
1085 BOOL doFileset = FALSE;
1086 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1088 CMD_LIST *thisCmdStart;
1091 /* Handle optional qualifiers (multiple are allowed) */
1092 while (*curPos && *curPos == '/') {
1093 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
1095 switch (toupperW(*curPos)) {
1096 case 'D': curPos++; expandDirs = TRUE; break;
1097 case 'L': curPos++; useNumbers = TRUE; break;
1099 /* Recursive is special case - /R can have an optional path following it */
1100 /* filenamesets are another special case - /F can have an optional options following it */
1104 BOOL isRecursive = (*curPos == 'R');
1109 /* Skip whitespace */
1111 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1113 /* Next parm is either qualifier, path/options or variable -
1114 only care about it if it is the path/options */
1115 if (*curPos && *curPos != '/' && *curPos != '%') {
1116 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
1118 static unsigned int once;
1119 if (!once++) WINE_FIXME("/F needs to handle options\n");
1125 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
1129 /* Skip whitespace between qualifiers */
1130 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1133 /* Skip whitespace before variable */
1134 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1136 /* Ensure line continues with variable */
1137 if (!*curPos || *curPos != '%') {
1138 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1142 /* Variable should follow */
1144 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
1145 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
1147 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1148 curPos = &curPos[i];
1150 /* Skip whitespace before IN */
1151 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1153 /* Ensure line continues with IN */
1155 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
1157 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1161 /* Save away where the set of data starts and the variable */
1162 thisDepth = (*cmdList)->bracketDepth;
1163 *cmdList = (*cmdList)->nextcommand;
1164 setStart = (*cmdList);
1166 /* Skip until the close bracket */
1167 WINE_TRACE("Searching %p as the set\n", *cmdList);
1169 (*cmdList)->command != NULL &&
1170 (*cmdList)->bracketDepth > thisDepth) {
1171 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1172 *cmdList = (*cmdList)->nextcommand;
1175 /* Skip the close bracket, if there is one */
1176 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1178 /* Syntax error if missing close bracket, or nothing following it
1179 and once we have the complete set, we expect a DO */
1180 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1181 if ((*cmdList == NULL)
1182 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1184 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1188 /* Save away the starting position for the commands (and offset for the
1190 cmdStart = *cmdList;
1192 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1196 /* Loop through all set entries */
1198 thisSet->command != NULL &&
1199 thisSet->bracketDepth >= thisDepth) {
1201 /* Loop through all entries on the same line */
1205 WINE_TRACE("Processing for set %p\n", thisSet);
1207 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1210 * If the parameter within the set has a wildcard then search for matching files
1211 * otherwise do a literal substitution.
1213 static const WCHAR wildcards[] = {'*','?','\0'};
1214 thisCmdStart = cmdStart;
1217 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1219 if (!useNumbers && !doFileset) {
1220 if (strpbrkW (item, wildcards)) {
1221 hff = FindFirstFileW(item, &fd);
1222 if (hff != INVALID_HANDLE_VALUE) {
1224 BOOL isDirectory = FALSE;
1226 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1228 /* Handle as files or dirs appropriately, but ignore . and .. */
1229 if (isDirectory == expandDirs &&
1230 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1231 (strcmpW(fd.cFileName, dotW) != 0))
1233 thisCmdStart = cmdStart;
1234 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1235 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1236 fd.cFileName, FALSE, TRUE);
1239 } while (FindNextFileW(hff, &fd) != 0);
1243 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1246 } else if (useNumbers) {
1247 /* Convert the first 3 numbers to signed longs and save */
1248 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1249 /* else ignore them! */
1251 /* Filesets - either a list of files, or a command to run and parse the output */
1252 } else if (doFileset && *itemStart != '"') {
1255 WCHAR temp_file[MAX_PATH];
1257 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1258 wine_dbgstr_w(item));
1260 /* If backquote or single quote, we need to launch that command
1261 and parse the results - use a temporary file */
1262 if (*itemStart == '`' || *itemStart == '\'') {
1264 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1265 static const WCHAR redirOut[] = {'>','%','s','\0'};
1266 static const WCHAR cmdW[] = {'C','M','D','\0'};
1268 /* Remove trailing character */
1269 itemStart[strlenW(itemStart)-1] = 0x00;
1271 /* Get temp filename */
1272 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1273 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1275 /* Execute program and redirect output */
1276 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1277 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1279 /* Open the file, read line by line and process */
1280 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1281 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1284 /* Open the file, read line by line and process */
1285 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1286 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1289 /* Process the input file */
1290 if (input == INVALID_HANDLE_VALUE) {
1291 WCMD_print_error ();
1292 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1294 return; /* FOR loop aborts at first failure here */
1298 WCHAR buffer[MAXSTRING] = {'\0'};
1299 WCHAR *where, *parm;
1301 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1303 /* Skip blank lines*/
1304 parm = WCMD_parameter (buffer, 0, &where, NULL);
1305 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1306 wine_dbgstr_w(buffer));
1309 /* FIXME: The following should be moved into its own routine and
1310 reused for the string literal parsing below */
1311 thisCmdStart = cmdStart;
1312 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1313 cmdEnd = thisCmdStart;
1319 CloseHandle (input);
1322 /* Delete the temporary file */
1323 if (*itemStart == '`' || *itemStart == '\'') {
1324 DeleteFileW(temp_file);
1327 /* Filesets - A string literal */
1328 } else if (doFileset && *itemStart == '"') {
1329 WCHAR buffer[MAXSTRING] = {'\0'};
1330 WCHAR *where, *parm;
1332 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1333 strcpyW(buffer, item);
1334 parm = WCMD_parameter (buffer, 0, &where, NULL);
1335 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1336 wine_dbgstr_w(buffer));
1339 /* FIXME: The following should be moved into its own routine and
1340 reused for the string literal parsing below */
1341 thisCmdStart = cmdStart;
1342 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1343 cmdEnd = thisCmdStart;
1347 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1348 cmdEnd = thisCmdStart;
1352 /* Move onto the next set line */
1353 thisSet = thisSet->nextcommand;
1356 /* If /L is provided, now run the for loop */
1359 static const WCHAR fmt[] = {'%','d','\0'};
1361 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1362 numbers[0], numbers[2], numbers[1]);
1364 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1367 sprintfW(thisNum, fmt, i);
1368 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1370 thisCmdStart = cmdStart;
1371 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1372 cmdEnd = thisCmdStart;
1376 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1377 all processing, OR it should be pointing to the end of && processing OR
1378 it should be pointing at the NULL end of bracket for the DO. The return
1379 value needs to be the NEXT command to execute, which it either is, or
1380 we need to step over the closing bracket */
1382 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1385 /**************************************************************************
1388 * Simple on-line help. Help text is stored in the resource file.
1391 void WCMD_give_help (const WCHAR *command)
1395 command = WCMD_skip_leading_spaces((WCHAR*) command);
1396 if (strlenW(command) == 0) {
1397 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1400 /* Display help message for builtin commands */
1401 for (i=0; i<=WCMD_EXIT; i++) {
1402 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1403 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1404 WCMD_output_asis (WCMD_LoadMessage(i));
1408 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1409 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1410 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1411 command, -1, externals[i], -1) == CSTR_EQUAL) {
1413 static const WCHAR helpW[] = {' ', '/','?','\0'};
1414 strcpyW(cmd, command);
1415 strcatW(cmd, helpW);
1416 WCMD_run_program(cmd, 0);
1420 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1425 /****************************************************************************
1428 * Batch file jump instruction. Not the most efficient algorithm ;-)
1429 * Prints error message if the specified label cannot be found - the file pointer is
1430 * then at EOF, effectively stopping the batch file.
1431 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1434 void WCMD_goto (CMD_LIST **cmdList) {
1436 WCHAR string[MAX_PATH];
1437 WCHAR current[MAX_PATH];
1439 /* Do not process any more parts of a processed multipart or multilines command */
1440 if (cmdList) *cmdList = NULL;
1442 if (context != NULL) {
1443 WCHAR *paramStart = param1, *str;
1444 static const WCHAR eofW[] = {':','e','o','f','\0'};
1446 if (param1[0] == 0x00) {
1447 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1451 /* Handle special :EOF label */
1452 if (lstrcmpiW (eofW, param1) == 0) {
1453 context -> skip_rest = TRUE;
1457 /* Support goto :label as well as goto label */
1458 if (*paramStart == ':') paramStart++;
1460 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1461 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1463 while (isspaceW (*str)) str++;
1467 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1470 /* ignore space at the end */
1472 if (lstrcmpiW (current, paramStart) == 0) return;
1475 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1480 /*****************************************************************************
1483 * Push a directory onto the stack
1486 void WCMD_pushd (const WCHAR *command)
1488 struct env_stack *curdir;
1490 static const WCHAR parmD[] = {'/','D','\0'};
1492 if (strchrW(command, '/') != NULL) {
1493 SetLastError(ERROR_INVALID_PARAMETER);
1498 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1499 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1500 if( !curdir || !thisdir ) {
1503 WINE_ERR ("out of memory\n");
1507 /* Change directory using CD code with /D parameter */
1508 strcpyW(quals, parmD);
1509 GetCurrentDirectoryW (1024, thisdir);
1511 WCMD_setshow_default(command);
1517 curdir -> next = pushd_directories;
1518 curdir -> strings = thisdir;
1519 if (pushd_directories == NULL) {
1520 curdir -> u.stackdepth = 1;
1522 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1524 pushd_directories = curdir;
1529 /*****************************************************************************
1532 * Pop a directory from the stack
1535 void WCMD_popd (void) {
1536 struct env_stack *temp = pushd_directories;
1538 if (!pushd_directories)
1541 /* pop the old environment from the stack, and make it the current dir */
1542 pushd_directories = temp->next;
1543 SetCurrentDirectoryW(temp->strings);
1544 LocalFree (temp->strings);
1548 /****************************************************************************
1551 * Batch file conditional.
1553 * On entry, cmdlist will point to command containing the IF, and optionally
1554 * the first command to execute (if brackets not found)
1555 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1556 * If ('s were found, execute all within that bracket
1557 * Command may optionally be followed by an ELSE - need to skip instructions
1558 * in the else using the same logic
1560 * FIXME: Much more syntax checking needed!
1563 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1565 int negate; /* Negate condition */
1566 int test; /* Condition evaluation result */
1567 WCHAR condition[MAX_PATH], *command, *s;
1568 static const WCHAR notW[] = {'n','o','t','\0'};
1569 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1570 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1571 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1572 static const WCHAR eqeqW[] = {'=','=','\0'};
1573 static const WCHAR parmI[] = {'/','I','\0'};
1574 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1576 negate = !lstrcmpiW(param1,notW);
1577 strcpyW(condition, (negate ? param2 : param1));
1578 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1580 if (!lstrcmpiW (condition, errlvlW)) {
1581 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL);
1583 long int param_int = strtolW(param, &endptr, 10);
1585 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1588 test = ((long int)errorlevel >= param_int);
1589 WCMD_parameter(p, 2+negate, &command, NULL);
1591 else if (!lstrcmpiW (condition, existW)) {
1592 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1593 WCMD_parameter(p, 2+negate, &command, NULL);
1595 else if (!lstrcmpiW (condition, defdW)) {
1596 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1597 WCMD_parameter(p, 2+negate, &command, NULL);
1599 else if ((s = strstrW (p, eqeqW))) {
1600 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1601 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1603 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1604 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1605 test = caseInsensitive
1606 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1607 leftPart, leftPartEnd-leftPart+1,
1608 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1609 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1610 leftPart, leftPartEnd-leftPart+1,
1611 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1612 WCMD_parameter(s, 1, &command, NULL);
1615 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1619 /* Process rest of IF statement which is on the same line
1620 Note: This may process all or some of the cmdList (eg a GOTO) */
1621 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1624 /****************************************************************************
1627 * Move a file, directory tree or wildcarded set of files.
1630 void WCMD_move (void)
1633 WIN32_FIND_DATAW fd;
1635 WCHAR input[MAX_PATH];
1636 WCHAR output[MAX_PATH];
1638 WCHAR dir[MAX_PATH];
1639 WCHAR fname[MAX_PATH];
1640 WCHAR ext[MAX_PATH];
1642 if (param1[0] == 0x00) {
1643 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1647 /* If no destination supplied, assume current directory */
1648 if (param2[0] == 0x00) {
1649 strcpyW(param2, dotW);
1652 /* If 2nd parm is directory, then use original filename */
1653 /* Convert partial path to full path */
1654 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1655 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1656 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1657 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1659 /* Split into components */
1660 WCMD_splitpath(input, drive, dir, fname, ext);
1662 hff = FindFirstFileW(input, &fd);
1663 if (hff == INVALID_HANDLE_VALUE)
1667 WCHAR dest[MAX_PATH];
1668 WCHAR src[MAX_PATH];
1672 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1674 /* Build src & dest name */
1675 strcpyW(src, drive);
1678 /* See if dest is an existing directory */
1679 attribs = GetFileAttributesW(output);
1680 if (attribs != INVALID_FILE_ATTRIBUTES &&
1681 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1682 strcpyW(dest, output);
1683 strcatW(dest, slashW);
1684 strcatW(dest, fd.cFileName);
1686 strcpyW(dest, output);
1689 strcatW(src, fd.cFileName);
1691 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1692 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1694 /* If destination exists, prompt unless /Y supplied */
1695 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1697 WCHAR copycmd[MAXSTRING];
1700 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1701 if (strstrW (quals, parmNoY))
1703 else if (strstrW (quals, parmY))
1706 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1707 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1708 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1709 && ! lstrcmpiW (copycmd, parmY));
1712 /* Prompt if overwriting */
1714 WCHAR question[MAXSTRING];
1717 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1719 /* Ask for confirmation */
1720 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1721 ok = WCMD_ask_confirm(question, FALSE, NULL);
1723 /* So delete the destination prior to the move */
1725 if (!DeleteFileW(dest)) {
1726 WCMD_print_error ();
1735 status = MoveFileW(src, dest);
1737 status = 1; /* Anything other than 0 to prevent error msg below */
1741 WCMD_print_error ();
1744 } while (FindNextFileW(hff, &fd) != 0);
1749 /****************************************************************************
1752 * Suspend execution of a batch script until a key is typed
1755 void WCMD_pause (void)
1761 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1763 have_console = GetConsoleMode(hIn, &oldmode);
1765 SetConsoleMode(hIn, 0);
1767 WCMD_output_asis(anykey);
1768 WCMD_ReadFile(hIn, &key, 1, &count);
1770 SetConsoleMode(hIn, oldmode);
1773 /****************************************************************************
1776 * Delete a directory.
1779 void WCMD_remove_dir (WCHAR *command) {
1782 int argsProcessed = 0;
1783 WCHAR *argN = command;
1784 static const WCHAR parmS[] = {'/','S','\0'};
1785 static const WCHAR parmQ[] = {'/','Q','\0'};
1787 /* Loop through all args */
1789 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1790 if (argN && argN[0] != '/') {
1791 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1792 wine_dbgstr_w(quals));
1795 /* If subdirectory search not supplied, just try to remove
1796 and report error if it fails (eg if it contains a file) */
1797 if (strstrW (quals, parmS) == NULL) {
1798 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1800 /* Otherwise use ShFileOp to recursively remove a directory */
1803 SHFILEOPSTRUCTW lpDir;
1806 if (strstrW (quals, parmQ) == NULL) {
1808 WCHAR question[MAXSTRING];
1809 static const WCHAR fmt[] = {'%','s',' ','\0'};
1811 /* Ask for confirmation */
1812 wsprintfW(question, fmt, thisArg);
1813 ok = WCMD_ask_confirm(question, TRUE, NULL);
1815 /* Abort if answer is 'N' */
1822 lpDir.pFrom = thisArg;
1823 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1824 lpDir.wFunc = FO_DELETE;
1825 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1830 /* Handle no valid args */
1831 if (argsProcessed == 0) {
1832 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1838 /****************************************************************************
1844 void WCMD_rename (void)
1848 WIN32_FIND_DATAW fd;
1849 WCHAR input[MAX_PATH];
1850 WCHAR *dotDst = NULL;
1852 WCHAR dir[MAX_PATH];
1853 WCHAR fname[MAX_PATH];
1854 WCHAR ext[MAX_PATH];
1858 /* Must be at least two args */
1859 if (param1[0] == 0x00 || param2[0] == 0x00) {
1860 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1865 /* Destination cannot contain a drive letter or directory separator */
1866 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1867 SetLastError(ERROR_INVALID_PARAMETER);
1873 /* Convert partial path to full path */
1874 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1875 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1876 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1877 dotDst = strchrW(param2, '.');
1879 /* Split into components */
1880 WCMD_splitpath(input, drive, dir, fname, ext);
1882 hff = FindFirstFileW(input, &fd);
1883 if (hff == INVALID_HANDLE_VALUE)
1887 WCHAR dest[MAX_PATH];
1888 WCHAR src[MAX_PATH];
1889 WCHAR *dotSrc = NULL;
1892 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1894 /* FIXME: If dest name or extension is *, replace with filename/ext
1895 part otherwise use supplied name. This supports:
1897 ren jim.* fred.* etc
1898 However, windows has a more complex algorithm supporting eg
1899 ?'s and *'s mid name */
1900 dotSrc = strchrW(fd.cFileName, '.');
1902 /* Build src & dest name */
1903 strcpyW(src, drive);
1906 dirLen = strlenW(src);
1907 strcatW(src, fd.cFileName);
1910 if (param2[0] == '*') {
1911 strcatW(dest, fd.cFileName);
1912 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1914 strcatW(dest, param2);
1915 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1918 /* Build Extension */
1919 if (dotDst && (*(dotDst+1)=='*')) {
1920 if (dotSrc) strcatW(dest, dotSrc);
1921 } else if (dotDst) {
1922 if (dotDst) strcatW(dest, dotDst);
1925 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1926 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1928 status = MoveFileW(src, dest);
1931 WCMD_print_error ();
1934 } while (FindNextFileW(hff, &fd) != 0);
1939 /*****************************************************************************
1942 * Make a copy of the environment.
1944 static WCHAR *WCMD_dupenv( const WCHAR *env )
1954 len += (strlenW(&env[len]) + 1);
1956 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1959 WINE_ERR("out of memory\n");
1962 memcpy (env_copy, env, len*sizeof (WCHAR));
1968 /*****************************************************************************
1971 * setlocal pushes the environment onto a stack
1972 * Save the environment as unicode so we don't screw anything up.
1974 void WCMD_setlocal (const WCHAR *s) {
1976 struct env_stack *env_copy;
1977 WCHAR cwd[MAX_PATH];
1979 /* DISABLEEXTENSIONS ignored */
1981 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1984 WINE_ERR ("out of memory\n");
1988 env = GetEnvironmentStringsW ();
1990 env_copy->strings = WCMD_dupenv (env);
1991 if (env_copy->strings)
1993 env_copy->next = saved_environment;
1994 saved_environment = env_copy;
1996 /* Save the current drive letter */
1997 GetCurrentDirectoryW(MAX_PATH, cwd);
1998 env_copy->u.cwd = cwd[0];
2001 LocalFree (env_copy);
2003 FreeEnvironmentStringsW (env);
2007 /*****************************************************************************
2010 * endlocal pops the environment off a stack
2011 * Note: When searching for '=', search from WCHAR position 1, to handle
2012 * special internal environment variables =C:, =D: etc
2014 void WCMD_endlocal (void) {
2015 WCHAR *env, *old, *p;
2016 struct env_stack *temp;
2019 if (!saved_environment)
2022 /* pop the old environment from the stack */
2023 temp = saved_environment;
2024 saved_environment = temp->next;
2026 /* delete the current environment, totally */
2027 env = GetEnvironmentStringsW ();
2028 old = WCMD_dupenv (GetEnvironmentStringsW ());
2031 n = strlenW(&old[len]) + 1;
2032 p = strchrW(&old[len] + 1, '=');
2036 SetEnvironmentVariableW (&old[len], NULL);
2041 FreeEnvironmentStringsW (env);
2043 /* restore old environment */
2044 env = temp->strings;
2047 n = strlenW(&env[len]) + 1;
2048 p = strchrW(&env[len] + 1, '=');
2052 SetEnvironmentVariableW (&env[len], p);
2057 /* Restore current drive letter */
2058 if (IsCharAlphaW(temp->u.cwd)) {
2060 WCHAR cwd[MAX_PATH];
2061 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2063 wsprintfW(envvar, fmt, temp->u.cwd);
2064 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2065 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2066 SetCurrentDirectoryW(cwd);
2074 /*****************************************************************************
2075 * WCMD_setshow_default
2077 * Set/Show the current default directory
2080 void WCMD_setshow_default (const WCHAR *command) {
2086 WIN32_FIND_DATAW fd;
2088 static const WCHAR parmD[] = {'/','D','\0'};
2090 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2092 /* Skip /D and trailing whitespace if on the front of the command line */
2093 if (CompareStringW(LOCALE_USER_DEFAULT,
2094 NORM_IGNORECASE | SORT_STRINGSORT,
2095 command, 2, parmD, -1) == CSTR_EQUAL) {
2097 while (*command && (*command==' ' || *command=='\t'))
2101 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2102 if (strlenW(command) == 0) {
2103 strcatW (cwd, newline);
2104 WCMD_output_asis (cwd);
2107 /* Remove any double quotes, which may be in the
2108 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2111 if (*command != '"') *pos++ = *command;
2114 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2118 /* Search for appropriate directory */
2119 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2120 hff = FindFirstFileW(string, &fd);
2121 if (hff != INVALID_HANDLE_VALUE) {
2123 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2124 WCHAR fpath[MAX_PATH];
2126 WCHAR dir[MAX_PATH];
2127 WCHAR fname[MAX_PATH];
2128 WCHAR ext[MAX_PATH];
2129 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2131 /* Convert path into actual directory spec */
2132 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2133 WCMD_splitpath(fpath, drive, dir, fname, ext);
2136 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2139 } while (FindNextFileW(hff, &fd) != 0);
2143 /* Change to that directory */
2144 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2146 status = SetCurrentDirectoryW(string);
2149 WCMD_print_error ();
2153 /* Save away the actual new directory, to store as current location */
2154 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2156 /* Restore old directory if drive letter would change, and
2157 CD x:\directory /D (or pushd c:\directory) not supplied */
2158 if ((strstrW(quals, parmD) == NULL) &&
2159 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2160 SetCurrentDirectoryW(cwd);
2164 /* Set special =C: type environment variable, for drive letter of
2165 change of directory, even if path was restored due to missing
2166 /D (allows changing drive letter when not resident on that
2168 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2170 strcpyW(env, equalW);
2171 memcpy(env+1, string, 2 * sizeof(WCHAR));
2173 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2174 SetEnvironmentVariableW(env, string);
2181 /****************************************************************************
2184 * Set/Show the system date
2185 * FIXME: Can't change date yet
2188 void WCMD_setshow_date (void) {
2190 WCHAR curdate[64], buffer[64];
2192 static const WCHAR parmT[] = {'/','T','\0'};
2194 if (strlenW(param1) == 0) {
2195 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2196 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2197 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2198 if (strstrW (quals, parmT) == NULL) {
2199 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2200 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2202 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2206 else WCMD_print_error ();
2209 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2213 /****************************************************************************
2216 static int WCMD_compare( const void *a, const void *b )
2219 const WCHAR * const *str_a = a, * const *str_b = b;
2220 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2221 *str_a, -1, *str_b, -1 );
2222 if( r == CSTR_LESS_THAN ) return -1;
2223 if( r == CSTR_GREATER_THAN ) return 1;
2227 /****************************************************************************
2228 * WCMD_setshow_sortenv
2230 * sort variables into order for display
2231 * Optionally only display those who start with a stub
2232 * returns the count displayed
2234 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2236 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2239 if (stub) stublen = strlenW(stub);
2241 /* count the number of strings, and the total length */
2243 len += (strlenW(&s[len]) + 1);
2247 /* add the strings to an array */
2248 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2252 for( i=1; i<count; i++ )
2253 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2255 /* sort the array */
2256 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2259 for( i=0; i<count; i++ ) {
2260 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2261 NORM_IGNORECASE | SORT_STRINGSORT,
2262 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2263 /* Don't display special internal variables */
2264 if (str[i][0] != '=') {
2265 WCMD_output_asis(str[i]);
2266 WCMD_output_asis(newline);
2273 return displayedcount;
2276 /****************************************************************************
2279 * Set/Show the environment variables
2282 void WCMD_setshow_env (WCHAR *s) {
2287 static const WCHAR parmP[] = {'/','P','\0'};
2289 if (param1[0] == 0x00 && quals[0] == 0x00) {
2290 env = GetEnvironmentStringsW();
2291 WCMD_setshow_sortenv( env, NULL );
2295 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2296 if (CompareStringW(LOCALE_USER_DEFAULT,
2297 NORM_IGNORECASE | SORT_STRINGSORT,
2298 s, 2, parmP, -1) == CSTR_EQUAL) {
2299 WCHAR string[MAXSTRING];
2303 while (*s && (*s==' ' || *s=='\t')) s++;
2305 WCMD_strip_quotes(s);
2307 /* If no parameter, or no '=' sign, return an error */
2308 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2309 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2313 /* Output the prompt */
2315 if (strlenW(p) != 0) WCMD_output_asis(p);
2317 /* Read the reply */
2318 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2320 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2321 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2322 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2323 wine_dbgstr_w(string));
2324 status = SetEnvironmentVariableW(s, string);
2331 WCMD_strip_quotes(s);
2332 p = strchrW (s, '=');
2334 env = GetEnvironmentStringsW();
2335 if (WCMD_setshow_sortenv( env, s ) == 0) {
2336 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2343 if (strlenW(p) == 0) p = NULL;
2344 status = SetEnvironmentVariableW(s, p);
2345 gle = GetLastError();
2346 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2348 } else if ((!status)) WCMD_print_error();
2352 /****************************************************************************
2355 * Set/Show the path environment variable
2358 void WCMD_setshow_path (const WCHAR *command) {
2362 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2363 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2365 if (strlenW(param1) == 0) {
2366 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2368 WCMD_output_asis ( pathEqW);
2369 WCMD_output_asis ( string);
2370 WCMD_output_asis ( newline);
2373 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2377 if (*command == '=') command++; /* Skip leading '=' */
2378 status = SetEnvironmentVariableW(pathW, command);
2379 if (!status) WCMD_print_error();
2383 /****************************************************************************
2384 * WCMD_setshow_prompt
2386 * Set or show the command prompt.
2389 void WCMD_setshow_prompt (void) {
2392 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2394 if (strlenW(param1) == 0) {
2395 SetEnvironmentVariableW(promptW, NULL);
2399 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2400 if (strlenW(s) == 0) {
2401 SetEnvironmentVariableW(promptW, NULL);
2403 else SetEnvironmentVariableW(promptW, s);
2407 /****************************************************************************
2410 * Set/Show the system time
2411 * FIXME: Can't change time yet
2414 void WCMD_setshow_time (void) {
2416 WCHAR curtime[64], buffer[64];
2419 static const WCHAR parmT[] = {'/','T','\0'};
2421 if (strlenW(param1) == 0) {
2423 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2424 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2425 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2426 if (strstrW (quals, parmT) == NULL) {
2427 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2428 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2430 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2434 else WCMD_print_error ();
2437 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2441 /****************************************************************************
2444 * Shift batch parameters.
2445 * Optional /n says where to start shifting (n=0-8)
2448 void WCMD_shift (const WCHAR *command) {
2451 if (context != NULL) {
2452 WCHAR *pos = strchrW(command, '/');
2457 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2458 start = (*(pos+1) - '0');
2460 SetLastError(ERROR_INVALID_PARAMETER);
2465 WINE_TRACE("Shifting variables, starting at %d\n", start);
2466 for (i=start;i<=8;i++) {
2467 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2469 context -> shift_count[9] = context -> shift_count[9] + 1;
2474 /****************************************************************************
2477 * Set the console title
2479 void WCMD_title (const WCHAR *command) {
2480 SetConsoleTitleW(command);
2483 /****************************************************************************
2486 * Copy a file to standard output.
2489 void WCMD_type (WCHAR *command) {
2492 WCHAR *argN = command;
2493 BOOL writeHeaders = FALSE;
2495 if (param1[0] == 0x00) {
2496 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2500 if (param2[0] != 0x00) writeHeaders = TRUE;
2502 /* Loop through all args */
2505 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2513 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2514 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2515 FILE_ATTRIBUTE_NORMAL, NULL);
2516 if (h == INVALID_HANDLE_VALUE) {
2517 WCMD_print_error ();
2518 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2522 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2523 WCMD_output(fmt, thisArg);
2525 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2526 if (count == 0) break; /* ReadFile reports success on EOF! */
2528 WCMD_output_asis (buffer);
2535 /****************************************************************************
2538 * Output either a file or stdin to screen in pages
2541 void WCMD_more (WCHAR *command) {
2544 WCHAR *argN = command;
2546 WCHAR moreStrPage[100];
2549 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2550 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2551 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2552 ')',' ','-','-','\n','\0'};
2553 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2555 /* Prefix the NLS more with '-- ', then load the text */
2557 strcpyW(moreStr, moreStart);
2558 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2559 (sizeof(moreStr)/sizeof(WCHAR))-3);
2561 if (param1[0] == 0x00) {
2563 /* Wine implements pipes via temporary files, and hence stdin is
2564 effectively reading from the file. This means the prompts for
2565 more are satisfied by the next line from the input (file). To
2566 avoid this, ensure stdin is to the console */
2567 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2568 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2569 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2570 FILE_ATTRIBUTE_NORMAL, 0);
2571 WINE_TRACE("No parms - working probably in pipe mode\n");
2572 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2574 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2575 once you get in this bit unless due to a pipe, its going to end badly... */
2576 wsprintfW(moreStrPage, moreFmt, moreStr);
2578 WCMD_enter_paged_mode(moreStrPage);
2579 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2580 if (count == 0) break; /* ReadFile reports success on EOF! */
2582 WCMD_output_asis (buffer);
2584 WCMD_leave_paged_mode();
2586 /* Restore stdin to what it was */
2587 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2588 CloseHandle(hConIn);
2592 BOOL needsPause = FALSE;
2594 /* Loop through all args */
2595 WINE_TRACE("Parms supplied - working through each file\n");
2596 WCMD_enter_paged_mode(moreStrPage);
2599 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2607 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2608 WCMD_leave_paged_mode();
2609 WCMD_output_asis(moreStrPage);
2610 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2611 WCMD_enter_paged_mode(moreStrPage);
2615 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2616 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2617 FILE_ATTRIBUTE_NORMAL, NULL);
2618 if (h == INVALID_HANDLE_VALUE) {
2619 WCMD_print_error ();
2620 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2624 ULONG64 fileLen = 0;
2625 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2627 /* Get the file size */
2628 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2629 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2632 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2633 if (count == 0) break; /* ReadFile reports success on EOF! */
2637 /* Update % count (would be used in WCMD_output_asis as prompt) */
2638 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2640 WCMD_output_asis (buffer);
2646 WCMD_leave_paged_mode();
2650 /****************************************************************************
2653 * Display verify flag.
2654 * FIXME: We don't actually do anything with the verify flag other than toggle
2658 void WCMD_verify (const WCHAR *command) {
2662 count = strlenW(command);
2664 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2665 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2668 if (lstrcmpiW(command, onW) == 0) {
2672 else if (lstrcmpiW(command, offW) == 0) {
2673 verify_mode = FALSE;
2676 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2679 /****************************************************************************
2682 * Display version info.
2685 void WCMD_version (void) {
2687 WCMD_output_asis (version_string);
2691 /****************************************************************************
2694 * Display volume information (set_label = FALSE)
2695 * Additionally set volume label (set_label = TRUE)
2696 * Returns 1 on success, 0 otherwise
2699 int WCMD_volume(BOOL set_label, const WCHAR *path)
2701 DWORD count, serial;
2702 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2705 if (strlenW(path) == 0) {
2706 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2708 WCMD_print_error ();
2711 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2712 &serial, NULL, NULL, NULL, 0);
2715 static const WCHAR fmt[] = {'%','s','\\','\0'};
2716 if ((path[1] != ':') || (strlenW(path) != 2)) {
2717 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2720 wsprintfW (curdir, fmt, path);
2721 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2726 WCMD_print_error ();
2729 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2730 curdir[0], label, HIWORD(serial), LOWORD(serial));
2732 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2733 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2735 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2736 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2738 if (strlenW(path) != 0) {
2739 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2742 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2748 /**************************************************************************
2751 * Exit either the process, or just this batch program
2755 void WCMD_exit (CMD_LIST **cmdList) {
2757 static const WCHAR parmB[] = {'/','B','\0'};
2758 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2760 if (context && lstrcmpiW(quals, parmB) == 0) {
2762 context -> skip_rest = TRUE;
2770 /*****************************************************************************
2773 * Lists or sets file associations (assoc = TRUE)
2774 * Lists or sets file types (assoc = FALSE)
2776 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2779 DWORD accessOptions = KEY_READ;
2781 LONG rc = ERROR_SUCCESS;
2782 WCHAR keyValue[MAXSTRING];
2783 DWORD valueLen = MAXSTRING;
2785 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2786 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2788 /* See if parameter includes '=' */
2790 newValue = strchrW(command, '=');
2791 if (newValue) accessOptions |= KEY_WRITE;
2793 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2794 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2795 accessOptions, &key) != ERROR_SUCCESS) {
2796 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2800 /* If no parameters then list all associations */
2801 if (*command == 0x00) {
2804 /* Enumerate all the keys */
2805 while (rc != ERROR_NO_MORE_ITEMS) {
2806 WCHAR keyName[MAXSTRING];
2809 /* Find the next value */
2810 nameLen = MAXSTRING;
2811 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2813 if (rc == ERROR_SUCCESS) {
2815 /* Only interested in extension ones if assoc, or others
2817 if ((keyName[0] == '.' && assoc) ||
2818 (!(keyName[0] == '.') && (!assoc)))
2820 WCHAR subkey[MAXSTRING];
2821 strcpyW(subkey, keyName);
2822 if (!assoc) strcatW(subkey, shOpCmdW);
2824 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2826 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2827 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2828 WCMD_output_asis(keyName);
2829 WCMD_output_asis(equalW);
2830 /* If no default value found, leave line empty after '=' */
2831 if (rc == ERROR_SUCCESS) {
2832 WCMD_output_asis(keyValue);
2834 WCMD_output_asis(newline);
2835 RegCloseKey(readKey);
2843 /* Parameter supplied - if no '=' on command line, its a query */
2844 if (newValue == NULL) {
2846 WCHAR subkey[MAXSTRING];
2848 /* Query terminates the parameter at the first space */
2849 strcpyW(keyValue, command);
2850 space = strchrW(keyValue, ' ');
2851 if (space) *space=0x00;
2853 /* Set up key name */
2854 strcpyW(subkey, keyValue);
2855 if (!assoc) strcatW(subkey, shOpCmdW);
2857 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2859 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2860 WCMD_output_asis(command);
2861 WCMD_output_asis(equalW);
2862 /* If no default value found, leave line empty after '=' */
2863 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2864 WCMD_output_asis(newline);
2865 RegCloseKey(readKey);
2868 WCHAR msgbuffer[MAXSTRING];
2869 WCHAR outbuffer[MAXSTRING];
2871 /* Load the translated 'File association not found' */
2873 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2875 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2877 wsprintfW(outbuffer, msgbuffer, keyValue);
2878 WCMD_output_asis_stderr(outbuffer);
2882 /* Not a query - its a set or clear of a value */
2885 WCHAR subkey[MAXSTRING];
2887 /* Get pointer to new value */
2891 /* Set up key name */
2892 strcpyW(subkey, command);
2893 if (!assoc) strcatW(subkey, shOpCmdW);
2895 /* If nothing after '=' then clear value - only valid for ASSOC */
2896 if (*newValue == 0x00) {
2898 if (assoc) rc = RegDeleteKeyW(key, command);
2899 if (assoc && rc == ERROR_SUCCESS) {
2900 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2902 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2907 WCHAR msgbuffer[MAXSTRING];
2908 WCHAR outbuffer[MAXSTRING];
2910 /* Load the translated 'File association not found' */
2912 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2913 sizeof(msgbuffer)/sizeof(WCHAR));
2915 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2916 sizeof(msgbuffer)/sizeof(WCHAR));
2918 wsprintfW(outbuffer, msgbuffer, keyValue);
2919 WCMD_output_asis_stderr(outbuffer);
2923 /* It really is a set value = contents */
2925 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2926 accessOptions, NULL, &readKey, NULL);
2927 if (rc == ERROR_SUCCESS) {
2928 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2930 sizeof(WCHAR) * (strlenW(newValue) + 1));
2931 RegCloseKey(readKey);
2934 if (rc != ERROR_SUCCESS) {
2938 WCMD_output_asis(command);
2939 WCMD_output_asis(equalW);
2940 WCMD_output_asis(newValue);
2941 WCMD_output_asis(newline);
2951 /****************************************************************************
2954 * Colors the terminal screen.
2957 void WCMD_color (void) {
2959 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2960 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2962 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2963 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
2967 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2973 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2978 /* Convert the color hex digits */
2979 if (param1[0] == 0x00) {
2980 color = defaultColor;
2982 color = strtoulW(param1, NULL, 16);
2985 /* Fail if fg == bg color */
2986 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2991 /* Set the current screen contents and ensure all future writes
2992 remain this color */
2993 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2994 SetConsoleTextAttribute(hStdOut, color);