2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
37 extern int defaultColor;
38 extern BOOL echo_mode;
39 extern BOOL interactive;
41 struct env_stack *pushd_directories;
42 const WCHAR dotW[] = {'.','\0'};
43 const WCHAR dotdotW[] = {'.','.','\0'};
44 const WCHAR nullW[] = {'\0'};
45 const WCHAR starW[] = {'*','\0'};
46 const WCHAR slashW[] = {'\\','\0'};
47 const WCHAR equalW[] = {'=','\0'};
48 const WCHAR inbuilt[][10] = {
49 {'C','A','L','L','\0'},
51 {'C','H','D','I','R','\0'},
53 {'C','O','P','Y','\0'},
54 {'C','T','T','Y','\0'},
55 {'D','A','T','E','\0'},
58 {'E','C','H','O','\0'},
59 {'E','R','A','S','E','\0'},
61 {'G','O','T','O','\0'},
62 {'H','E','L','P','\0'},
64 {'L','A','B','E','L','\0'},
66 {'M','K','D','I','R','\0'},
67 {'M','O','V','E','\0'},
68 {'P','A','T','H','\0'},
69 {'P','A','U','S','E','\0'},
70 {'P','R','O','M','P','T','\0'},
73 {'R','E','N','A','M','E','\0'},
75 {'R','M','D','I','R','\0'},
77 {'S','H','I','F','T','\0'},
78 {'S','T','A','R','T','\0'},
79 {'T','I','M','E','\0'},
80 {'T','I','T','L','E','\0'},
81 {'T','Y','P','E','\0'},
82 {'V','E','R','I','F','Y','\0'},
85 {'E','N','D','L','O','C','A','L','\0'},
86 {'S','E','T','L','O','C','A','L','\0'},
87 {'P','U','S','H','D','\0'},
88 {'P','O','P','D','\0'},
89 {'A','S','S','O','C','\0'},
90 {'C','O','L','O','R','\0'},
91 {'F','T','Y','P','E','\0'},
92 {'M','O','R','E','\0'},
93 {'C','H','O','I','C','E','\0'},
94 {'E','X','I','T','\0'}
96 static const WCHAR externals[][10] = {
97 {'A','T','T','R','I','B','\0'},
98 {'X','C','O','P','Y','\0'}
100 static const WCHAR fslashW[] = {'/','\0'};
101 static const WCHAR onW[] = {'O','N','\0'};
102 static const WCHAR offW[] = {'O','F','F','\0'};
103 static const WCHAR parmY[] = {'/','Y','\0'};
104 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
106 static HINSTANCE hinst;
107 struct env_stack *saved_environment;
108 static BOOL verify_mode = FALSE;
110 /**************************************************************************
113 * Issue a message and ask for confirmation, waiting on a valid answer.
115 * Returns True if Y (or A) answer is selected
116 * If optionAll contains a pointer, ALL is allowed, and if answered
120 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
124 WCHAR confirm[MAXSTRING];
125 WCHAR options[MAXSTRING];
126 WCHAR Ybuffer[MAXSTRING];
127 WCHAR Nbuffer[MAXSTRING];
128 WCHAR Abuffer[MAXSTRING];
129 WCHAR answer[MAX_PATH] = {'\0'};
132 /* Load the translated valid answers */
134 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
135 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
136 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
137 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
138 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
139 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
141 /* Loop waiting on a valid answer */
146 WCMD_output_asis (message);
148 WCMD_output_asis (confirm);
149 WCMD_output_asis (options);
150 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
151 answer[0] = toupperW(answer[0]);
152 if (answer[0] == Ybuffer[0])
154 if (answer[0] == Nbuffer[0])
156 if (optionAll && answer[0] == Abuffer[0])
164 /****************************************************************************
167 * Clear the terminal screen.
170 void WCMD_clear_screen (void) {
172 /* Emulate by filling the screen from the top left to bottom right with
173 spaces, then moving the cursor to the top left afterwards */
174 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
175 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
177 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
182 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
186 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
187 SetConsoleCursorPosition(hStdOut, topLeft);
191 /****************************************************************************
194 * Change the default i/o device (ie redirect STDin/STDout).
197 void WCMD_change_tty (void) {
199 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
203 /****************************************************************************
208 void WCMD_choice (const WCHAR * command) {
210 static const WCHAR bellW[] = {7,0};
211 static const WCHAR commaW[] = {',',0};
212 static const WCHAR bracket_open[] = {'[',0};
213 static const WCHAR bracket_close[] = {']','?',0};
218 WCHAR *my_command = NULL;
219 WCHAR opt_default = 0;
220 DWORD opt_timeout = 0;
227 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
230 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
234 ptr = WCMD_skip_leading_spaces(my_command);
235 while (*ptr == '/') {
236 switch (toupperW(ptr[1])) {
239 /* the colon is optional */
243 if (!*ptr || isspaceW(*ptr)) {
244 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
245 HeapFree(GetProcessHeap(), 0, my_command);
249 /* remember the allowed keys (overwrite previous /C option) */
251 while (*ptr && (!isspaceW(*ptr)))
255 /* terminate allowed chars */
257 ptr = WCMD_skip_leading_spaces(&ptr[1]);
259 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
264 ptr = WCMD_skip_leading_spaces(&ptr[2]);
269 ptr = WCMD_skip_leading_spaces(&ptr[2]);
274 /* the colon is optional */
278 opt_default = *ptr++;
280 if (!opt_default || (*ptr != ',')) {
281 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
282 HeapFree(GetProcessHeap(), 0, my_command);
288 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
294 opt_timeout = atoiW(answer);
296 ptr = WCMD_skip_leading_spaces(ptr);
300 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
301 HeapFree(GetProcessHeap(), 0, my_command);
307 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
310 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
312 /* use default keys, when needed: localized versions of "Y"es and "No" */
314 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
315 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
320 /* print the question, when needed */
322 WCMD_output_asis(ptr);
326 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
330 /* print a list of all allowed answers inside brackets */
331 WCMD_output_asis(bracket_open);
334 while ((answer[0] = *ptr++)) {
335 WCMD_output_asis(answer);
337 WCMD_output_asis(commaW);
339 WCMD_output_asis(bracket_close);
344 /* FIXME: Add support for option /T */
345 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
348 answer[0] = toupperW(answer[0]);
350 ptr = strchrW(opt_c, answer[0]);
352 WCMD_output_asis(answer);
353 WCMD_output_asis(newlineW);
355 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
357 errorlevel = (ptr - opt_c) + 1;
358 WINE_TRACE("answer: %d\n", errorlevel);
359 HeapFree(GetProcessHeap(), 0, my_command);
364 /* key not allowed: play the bell */
365 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
366 WCMD_output_asis(bellW);
371 /****************************************************************************
374 * Copy a file or wildcarded set.
375 * FIXME: Add support for a+b+c type syntax
378 void WCMD_copy(WCHAR * command) {
380 BOOL opt_d, opt_v, opt_n, opt_z;
386 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
387 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
388 BOOL anyconcats = FALSE; /* Have we found any + options */
391 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
393 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
394 BOOL copyToDir = FALSE;
395 WCHAR srcspec[MAX_PATH];
399 WCHAR fname[MAX_PATH];
402 typedef struct _COPY_FILES
404 struct _COPY_FILES *next;
409 COPY_FILES *sourcelist = NULL;
410 COPY_FILES *lastcopyentry = NULL;
411 COPY_FILES *destination = NULL;
412 COPY_FILES *thiscopy = NULL;
413 COPY_FILES *prevcopy = NULL;
415 /* If no args supplied at all, report an error */
416 if (param1[0] == 0x00) {
417 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
421 opt_d = opt_v = opt_n = opt_z = FALSE;
423 /* Walk through all args, building up a list of files to process */
424 thisparam = WCMD_parameter(command, argno++, &rawarg, NULL, TRUE);
425 while (*(thisparam)) {
429 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
431 /* Handle switches */
432 if (*thisparam == '/') {
433 while (*thisparam == '/') {
435 if (toupperW(*thisparam) == 'D') {
437 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
438 } else if (toupperW(*thisparam) == 'V') {
440 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
441 } else if (toupperW(*thisparam) == 'N') {
443 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
444 } else if (toupperW(*thisparam) == 'Z') {
446 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
447 } else if (toupperW(*thisparam) == 'A') {
448 if (binarymode != 0) {
450 WINE_TRACE("Subsequent files will be handled as ASCII\n");
451 if (destination != NULL) {
452 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
453 destination->binarycopy = binarymode;
454 } else if (lastcopyentry != NULL) {
455 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
456 lastcopyentry->binarycopy = binarymode;
459 } else if (toupperW(*thisparam) == 'B') {
460 if (binarymode != 1) {
462 WINE_TRACE("Subsequent files will be handled as binary\n");
463 if (destination != NULL) {
464 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
465 destination->binarycopy = binarymode;
466 } else if (lastcopyentry != NULL) {
467 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
468 lastcopyentry->binarycopy = binarymode;
472 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
477 /* This parameter was purely switches, get the next one */
478 thisparam = WCMD_parameter(command, argno++, &rawarg, NULL, TRUE);
482 /* We have found something which is not a switch. If could be anything of the form
483 sourcefilename (which could be destination too)
484 + (when filename + filename syntex used)
485 sourcefilename+sourcefilename
487 +/b[tests show windows then ignores to end of parameter]
490 if (*thisparam=='+') {
491 if (lastcopyentry == NULL) {
492 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
496 concatnextfilename = TRUE;
500 /* Move to next thing to process */
502 if (*thisparam == 0x00) thisparam = WCMD_parameter(command, argno++, &rawarg, NULL, TRUE);
506 /* We have found something to process - build a COPY_FILE block to store it */
507 thiscopy = HeapAlloc(GetProcessHeap(),0,sizeof(COPY_FILES));
508 if (thiscopy == NULL) goto exitreturn;
511 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
512 thiscopy->concatenate = concatnextfilename;
513 thiscopy->binarycopy = binarymode;
514 thiscopy->next = NULL;
516 /* Time to work out the name. Allocate at least enough space (deliberately too much to
517 leave space to append \* to the end) , then copy in character by character. Strip off
518 quotes if we find them. */
519 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
520 thiscopy->name = HeapAlloc(GetProcessHeap(),0,len*sizeof(WCHAR));
521 memset(thiscopy->name, 0x00, len);
524 pos2 = thiscopy->name;
526 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
528 inquotes = !inquotes;
530 } else *pos2++ = *pos1++;
533 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
535 /* This is either the first source, concatenated subsequent source or destination */
536 if (sourcelist == NULL) {
537 WINE_TRACE("Adding as first source part\n");
538 sourcelist = thiscopy;
539 lastcopyentry = thiscopy;
540 } else if (concatnextfilename) {
541 WINE_TRACE("Adding to source file list to be concatenated\n");
542 lastcopyentry->next = thiscopy;
543 lastcopyentry = thiscopy;
544 } else if (destination == NULL) {
545 destination = thiscopy;
547 /* We have processed sources and destinations and still found more to do - invalid */
548 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
552 concatnextfilename = FALSE;
554 /* We either need to process the rest of the parameter or move to the next */
555 if (*pos1 == '/' || *pos1 == '+') {
559 thisparam = WCMD_parameter(command, argno++, &rawarg, NULL, TRUE);
563 /* Ensure we have at least one source file */
565 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
570 /* Convert source into full spec */
571 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(sourcelist->name));
572 GetFullPathNameW(sourcelist->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
573 if (srcpath[strlenW(srcpath) - 1] == '\\')
574 srcpath[strlenW(srcpath) - 1] = '\0';
576 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
577 attribs = GetFileAttributesW(srcpath);
581 strcpyW(srcspec, srcpath);
583 /* If a directory, then add \* on the end when searching */
584 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
585 strcatW(srcpath, slashW);
586 strcatW(srcspec, slashW);
587 strcatW(srcspec, starW);
589 WCMD_splitpath(srcpath, drive, dir, fname, ext);
590 strcpyW(srcpath, drive);
591 strcatW(srcpath, dir);
594 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
595 wine_dbgstr_w(srcpath), anyconcats);
597 /* Temporarily use param2 to hold destination */
598 /* If no destination supplied, assume current directory */
600 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(destination->name));
601 strcpyW(param2, destination->name);
603 WINE_TRACE("Copy destination not supplied\n");
604 strcpyW(param2, dotW);
607 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
608 if (outpath[strlenW(outpath) - 1] == '\\')
609 outpath[strlenW(outpath) - 1] = '\0';
610 attribs = GetFileAttributesW(outpath);
611 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
612 strcatW (outpath, slashW);
615 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
616 wine_dbgstr_w(outpath), copyToDir);
618 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
619 if (strstrW (quals, parmNoY))
621 else if (strstrW (quals, parmY))
624 /* By default, we will force the overwrite in batch mode and ask for
625 * confirmation in interactive mode. */
626 force = !interactive;
628 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
629 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
630 * default behavior. */
631 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
632 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
633 if (!lstrcmpiW (copycmd, parmY))
635 else if (!lstrcmpiW (copycmd, parmNoY))
640 /* Loop through all source files */
641 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
642 hff = FindFirstFileW(srcspec, &fd);
643 if (hff != INVALID_HANDLE_VALUE) {
645 WCHAR outname[MAX_PATH];
646 WCHAR srcname[MAX_PATH];
647 BOOL overwrite = force;
649 /* Destination is either supplied filename, or source name in
650 supplied destination directory */
651 strcpyW(outname, outpath);
652 if (copyToDir) strcatW(outname, fd.cFileName);
653 strcpyW(srcname, srcpath);
654 strcatW(srcname, fd.cFileName);
656 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
657 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
659 /* Skip . and .., and directories */
660 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
662 WINE_TRACE("Skipping directories\n");
665 /* Prompt before overwriting */
666 else if (!overwrite) {
667 attribs = GetFileAttributesW(outname);
668 if (attribs != INVALID_FILE_ATTRIBUTES) {
670 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
671 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
674 else overwrite = TRUE;
677 /* Do the copy as appropriate */
679 status = CopyFileW(srcname, outname, FALSE);
686 } while (FindNextFileW(hff, &fd) != 0);
693 /* We were successful! */
696 /* Exit out of the routine, freeing any remaing allocated memory */
699 thiscopy = sourcelist;
700 while (thiscopy != NULL) {
702 /* Free up this block*/
703 thiscopy = thiscopy -> next;
704 HeapFree(GetProcessHeap(), 0, prevcopy->name);
705 HeapFree(GetProcessHeap(), 0, prevcopy);
708 /* Free up the destination memory */
710 HeapFree(GetProcessHeap(), 0, destination->name);
711 HeapFree(GetProcessHeap(), 0, destination);
717 /****************************************************************************
720 * Create a directory (and, if needed, any intermediate directories).
722 * Modifies its argument by replacing slashes temporarily with nulls.
725 static BOOL create_full_path(WCHAR* path)
729 /* don't mess with drive letter portion of path, if any */
734 /* Strip trailing slashes. */
735 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
738 /* Step through path, creating intermediate directories as needed. */
739 /* First component includes drive letter, if any. */
743 /* Skip to end of component */
744 while (*p == '\\') p++;
745 while (*p && *p != '\\') p++;
747 /* path is now the original full path */
748 return CreateDirectoryW(path, NULL);
750 /* Truncate path, create intermediate directory, and restore path */
752 rv = CreateDirectoryW(path, NULL);
754 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
761 void WCMD_create_dir (WCHAR *command) {
763 WCHAR *argN = command;
765 if (param1[0] == 0x00) {
766 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
769 /* Loop through all args */
771 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL, FALSE);
773 if (!create_full_path(thisArg)) {
780 /* Parse the /A options given by the user on the commandline
781 * into a bitmask of wanted attributes (*wantSet),
782 * and a bitmask of unwanted attributes (*wantClear).
784 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
785 static const WCHAR parmA[] = {'/','A','\0'};
788 /* both are strictly 'out' parameters */
792 /* For each /A argument */
793 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
797 /* Skip optional : */
800 /* For each of the attribute specifier chars to this /A option */
801 for (; *p != 0 && *p != '/'; p++) {
810 /* Convert the attribute specifier to a bit in one of the masks */
812 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
813 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
814 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
815 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
817 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
827 /* If filename part of parameter is * or *.*,
828 * and neither /Q nor /P options were given,
829 * prompt the user whether to proceed.
830 * Returns FALSE if user says no, TRUE otherwise.
831 * *pPrompted is set to TRUE if the user is prompted.
832 * (If /P supplied, del will prompt for individual files later.)
834 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
835 static const WCHAR parmP[] = {'/','P','\0'};
836 static const WCHAR parmQ[] = {'/','Q','\0'};
838 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
839 static const WCHAR anyExt[]= {'.','*','\0'};
842 WCHAR fname[MAX_PATH];
844 WCHAR fpath[MAX_PATH];
846 /* Convert path into actual directory spec */
847 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
848 WCMD_splitpath(fpath, drive, dir, fname, ext);
850 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
851 if ((strcmpW(fname, starW) == 0) &&
852 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
854 WCHAR question[MAXSTRING];
855 static const WCHAR fmt[] = {'%','s',' ','\0'};
857 /* Caller uses this to suppress "file not found" warning later */
860 /* Ask for confirmation */
861 wsprintfW(question, fmt, fpath);
862 return WCMD_ask_confirm(question, TRUE, NULL);
865 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
869 /* Helper function for WCMD_delete().
870 * Deletes a single file, directory, or wildcard.
871 * If /S was given, does it recursively.
872 * Returns TRUE if a file was deleted.
874 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
876 static const WCHAR parmP[] = {'/','P','\0'};
877 static const WCHAR parmS[] = {'/','S','\0'};
878 static const WCHAR parmF[] = {'/','F','\0'};
880 DWORD unwanted_attrs;
882 WCHAR argCopy[MAX_PATH];
885 WCHAR fpath[MAX_PATH];
887 BOOL handleParm = TRUE;
889 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
891 strcpyW(argCopy, thisArg);
892 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
893 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
895 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
896 /* Skip this arg if user declines to delete *.* */
900 /* First, try to delete in the current directory */
901 hff = FindFirstFileW(argCopy, &fd);
902 if (hff == INVALID_HANDLE_VALUE) {
908 /* Support del <dirname> by just deleting all files dirname\* */
910 && (strchrW(argCopy,'*') == NULL)
911 && (strchrW(argCopy,'?') == NULL)
912 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
914 WCHAR modifiedParm[MAX_PATH];
915 static const WCHAR slashStar[] = {'\\','*','\0'};
917 strcpyW(modifiedParm, argCopy);
918 strcatW(modifiedParm, slashStar);
921 WCMD_delete_one(modifiedParm);
923 } else if (handleParm) {
925 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
926 strcpyW (fpath, argCopy);
928 p = strrchrW (fpath, '\\');
931 strcatW (fpath, fd.cFileName);
933 else strcpyW (fpath, fd.cFileName);
934 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
937 /* Handle attribute matching (/A) */
938 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
939 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
941 /* /P means prompt for each file */
942 if (ok && strstrW (quals, parmP) != NULL) {
945 /* Ask for confirmation */
946 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
947 ok = WCMD_ask_confirm(question, FALSE, NULL);
951 /* Only proceed if ok to */
954 /* If file is read only, and /A:r or /F supplied, delete it */
955 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
956 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
957 strstrW (quals, parmF) != NULL)) {
958 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
961 /* Now do the delete */
962 if (!DeleteFileW(fpath)) WCMD_print_error ();
966 } while (FindNextFileW(hff, &fd) != 0);
970 /* Now recurse into all subdirectories handling the parameter in the same way */
971 if (strstrW (quals, parmS) != NULL) {
973 WCHAR thisDir[MAX_PATH];
978 WCHAR fname[MAX_PATH];
981 /* Convert path into actual directory spec */
982 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
983 WCMD_splitpath(thisDir, drive, dir, fname, ext);
985 strcpyW(thisDir, drive);
986 strcatW(thisDir, dir);
987 cPos = strlenW(thisDir);
989 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
991 /* Append '*' to the directory */
993 thisDir[cPos+1] = 0x00;
995 hff = FindFirstFileW(thisDir, &fd);
997 /* Remove residual '*' */
998 thisDir[cPos] = 0x00;
1000 if (hff != INVALID_HANDLE_VALUE) {
1001 DIRECTORY_STACK *allDirs = NULL;
1002 DIRECTORY_STACK *lastEntry = NULL;
1005 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1006 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1007 (strcmpW(fd.cFileName, dotW) != 0)) {
1009 DIRECTORY_STACK *nextDir;
1010 WCHAR subParm[MAX_PATH];
1012 /* Work out search parameter in sub dir */
1013 strcpyW (subParm, thisDir);
1014 strcatW (subParm, fd.cFileName);
1015 strcatW (subParm, slashW);
1016 strcatW (subParm, fname);
1017 strcatW (subParm, ext);
1018 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1020 /* Allocate memory, add to list */
1021 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
1022 if (allDirs == NULL) allDirs = nextDir;
1023 if (lastEntry != NULL) lastEntry->next = nextDir;
1024 lastEntry = nextDir;
1025 nextDir->next = NULL;
1026 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
1027 (strlenW(subParm)+1) * sizeof(WCHAR));
1028 strcpyW(nextDir->dirName, subParm);
1030 } while (FindNextFileW(hff, &fd) != 0);
1033 /* Go through each subdir doing the delete */
1034 while (allDirs != NULL) {
1035 DIRECTORY_STACK *tempDir;
1037 tempDir = allDirs->next;
1038 found |= WCMD_delete_one (allDirs->dirName);
1040 HeapFree(GetProcessHeap(),0,allDirs->dirName);
1041 HeapFree(GetProcessHeap(),0,allDirs);
1050 /****************************************************************************
1053 * Delete a file or wildcarded set.
1056 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1057 * - Each set is a pattern, eg /ahr /as-r means
1058 * readonly+hidden OR nonreadonly system files
1059 * - The '-' applies to a single field, ie /a:-hr means read only
1063 BOOL WCMD_delete (WCHAR *command) {
1066 BOOL argsProcessed = FALSE;
1067 BOOL foundAny = FALSE;
1071 for (argno=0; ; argno++) {
1076 thisArg = WCMD_parameter (command, argno, &argN, NULL, FALSE);
1078 break; /* no more parameters */
1080 continue; /* skip options */
1082 argsProcessed = TRUE;
1083 found = WCMD_delete_one(thisArg);
1086 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1091 /* Handle no valid args */
1093 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1101 * Returns a trimmed version of s with all leading and trailing whitespace removed
1105 static WCHAR *WCMD_strtrim(const WCHAR *s)
1107 DWORD len = strlenW(s);
1108 const WCHAR *start = s;
1111 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
1114 while (isspaceW(*start)) start++;
1116 const WCHAR *end = s + len - 1;
1117 while (end > start && isspaceW(*end)) end--;
1118 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1119 result[end - start + 1] = '\0';
1127 /****************************************************************************
1130 * Echo input to the screen (or not). We don't try to emulate the bugs
1131 * in DOS (try typing "ECHO ON AGAIN" for an example).
1134 void WCMD_echo (const WCHAR *command)
1137 const WCHAR *origcommand = command;
1140 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
1141 || command[0]==':' || command[0]==';')
1144 trimmed = WCMD_strtrim(command);
1145 if (!trimmed) return;
1147 count = strlenW(trimmed);
1148 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1149 && origcommand[0]!=';') {
1150 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1151 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1155 if (lstrcmpiW(trimmed, onW) == 0)
1157 else if (lstrcmpiW(trimmed, offW) == 0)
1160 WCMD_output_asis (command);
1161 WCMD_output_asis (newlineW);
1163 HeapFree(GetProcessHeap(), 0, trimmed);
1166 /*****************************************************************************
1169 * Execute a command, and any && or bracketed follow on to the command. The
1170 * first command to be executed may not be at the front of the
1171 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1173 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1174 const WCHAR *variable, const WCHAR *value,
1175 BOOL isIF, BOOL executecmds)
1177 CMD_LIST *curPosition = *cmdList;
1178 int myDepth = (*cmdList)->bracketDepth;
1180 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1181 cmdList, wine_dbgstr_w(firstcmd),
1182 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1185 /* Skip leading whitespace between condition and the command */
1186 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1188 /* Process the first command, if there is one */
1189 if (executecmds && firstcmd && *firstcmd) {
1190 WCHAR *command = WCMD_strdupW(firstcmd);
1191 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1192 HeapFree(GetProcessHeap(), 0, command);
1196 /* If it didn't move the position, step to next command */
1197 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1199 /* Process any other parts of the command */
1201 BOOL processThese = executecmds;
1204 static const WCHAR ifElse[] = {'e','l','s','e'};
1206 /* execute all appropriate commands */
1207 curPosition = *cmdList;
1209 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1211 (*cmdList)->prevDelim,
1212 (*cmdList)->bracketDepth, myDepth);
1214 /* Execute any statements appended to the line */
1215 /* FIXME: Only if previous call worked for && or failed for || */
1216 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1217 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1218 if (processThese && (*cmdList)->command) {
1219 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1222 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1224 /* Execute any appended to the statement with (...) */
1225 } else if ((*cmdList)->bracketDepth > myDepth) {
1227 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1228 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1230 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1232 /* End of the command - does 'ELSE ' follow as the next command? */
1235 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1236 (*cmdList)->command)) {
1238 /* Swap between if and else processing */
1239 processThese = !processThese;
1241 /* Process the ELSE part */
1243 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1244 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1246 /* Skip leading whitespace between condition and the command */
1247 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1249 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1252 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1254 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1263 /**************************************************************************
1266 * Batch file loop processing.
1268 * On entry: cmdList contains the syntax up to the set
1269 * next cmdList and all in that bracket contain the set data
1270 * next cmdlist contains the DO cmd
1271 * following that is either brackets or && entries (as per if)
1275 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1277 WIN32_FIND_DATAW fd;
1280 static const WCHAR inW[] = {'i','n'};
1281 static const WCHAR doW[] = {'d','o'};
1282 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1286 WCHAR optionsRoot[MAX_PATH];
1287 DIRECTORY_STACK *dirsToWalk = NULL;
1289 BOOL expandDirs = FALSE;
1290 BOOL useNumbers = FALSE;
1291 BOOL doFileset = FALSE;
1292 BOOL doRecurse = FALSE;
1293 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
1294 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1296 CMD_LIST *thisCmdStart;
1297 int parameterNo = 0;
1299 /* Handle optional qualifiers (multiple are allowed) */
1300 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1303 while (thisArg && *thisArg == '/') {
1304 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
1306 switch (toupperW(*thisArg)) {
1307 case 'D': expandDirs = TRUE; break;
1308 case 'L': useNumbers = TRUE; break;
1310 /* Recursive is special case - /R can have an optional path following it */
1311 /* filenamesets are another special case - /F can have an optional options following it */
1315 /* When recursing directories, use current directory as the starting point unless
1316 subsequently overridden */
1317 doRecurse = (toupperW(*thisArg) == 'R');
1318 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
1320 doFileset = (toupperW(*thisArg) == 'F');
1322 /* Retrieve next parameter to see if is root/options (raw form required
1323 with for /f, or unquoted in for /r) */
1324 thisArg = WCMD_parameter(p, parameterNo, NULL, NULL, doFileset);
1326 /* Next parm is either qualifier, path/options or variable -
1327 only care about it if it is the path/options */
1328 if (thisArg && *thisArg != '/' && *thisArg != '%') {
1330 strcpyW(optionsRoot, thisArg);
1332 static unsigned int once;
1333 if (!once++) WINE_FIXME("/F needs to handle options\n");
1339 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
1342 /* Step to next token */
1343 thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1346 /* Ensure line continues with variable */
1347 if (!*thisArg || *thisArg != '%') {
1348 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1352 /* Set up the list of directories to recurse if we are going to */
1354 /* Allocate memory, add to list */
1355 dirsToWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1356 dirsToWalk->next = NULL;
1357 dirsToWalk->dirName = HeapAlloc(GetProcessHeap(),0,
1358 (strlenW(optionsRoot) + 1) * sizeof(WCHAR));
1359 strcpyW(dirsToWalk->dirName, optionsRoot);
1360 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
1363 /* Variable should follow */
1364 strcpyW(variable, thisArg);
1365 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1367 /* Ensure line continues with IN */
1368 thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1370 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1371 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
1372 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
1373 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1377 /* Save away where the set of data starts and the variable */
1378 thisDepth = (*cmdList)->bracketDepth;
1379 *cmdList = (*cmdList)->nextcommand;
1380 setStart = (*cmdList);
1382 /* Skip until the close bracket */
1383 WINE_TRACE("Searching %p as the set\n", *cmdList);
1385 (*cmdList)->command != NULL &&
1386 (*cmdList)->bracketDepth > thisDepth) {
1387 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1388 *cmdList = (*cmdList)->nextcommand;
1391 /* Skip the close bracket, if there is one */
1392 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1394 /* Syntax error if missing close bracket, or nothing following it
1395 and once we have the complete set, we expect a DO */
1396 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1397 if ((*cmdList == NULL)
1398 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1400 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1406 /* Loop repeatedly per-directory we are potentially walking, when in for /r
1407 mode, or once for the rest of the time. */
1409 WCHAR fullitem[MAX_PATH];
1410 static const WCHAR slashstarW[] = {'\\','*','\0'};
1412 /* Save away the starting position for the commands (and offset for the
1414 cmdStart = *cmdList;
1415 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1418 /* If we are recursing directories (ie /R), add all sub directories now, then
1419 prefix the root when searching for the item */
1421 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1423 /* Build a generic search and add all directories on the list of directories
1425 strcpyW(fullitem, dirsToWalk->dirName);
1426 strcatW(fullitem, slashstarW);
1427 hff = FindFirstFileW(fullitem, &fd);
1428 if (hff != INVALID_HANDLE_VALUE) {
1430 WINE_TRACE("Looking for subdirectories\n");
1431 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1432 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1433 (strcmpW(fd.cFileName, dotW) != 0))
1435 /* Allocate memory, add to list */
1436 DIRECTORY_STACK *toWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1437 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1438 toWalk->next = remainingDirs->next;
1439 remainingDirs->next = toWalk;
1440 remainingDirs = toWalk;
1441 toWalk->dirName = HeapAlloc(GetProcessHeap(), 0,
1443 (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1444 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1445 strcatW(toWalk->dirName, slashW);
1446 strcatW(toWalk->dirName, fd.cFileName);
1447 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1448 toWalk, toWalk->next);
1450 } while (FindNextFileW(hff, &fd) != 0);
1451 WINE_TRACE("Finished adding all subdirectories\n");
1457 /* Loop through all set entries */
1459 thisSet->command != NULL &&
1460 thisSet->bracketDepth >= thisDepth) {
1462 /* Loop through all entries on the same line */
1466 WINE_TRACE("Processing for set %p\n", thisSet);
1468 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL, TRUE))) {
1471 * If the parameter within the set has a wildcard then search for matching files
1472 * otherwise do a literal substitution.
1474 static const WCHAR wildcards[] = {'*','?','\0'};
1475 thisCmdStart = cmdStart;
1478 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1480 if (!useNumbers && !doFileset) {
1481 WCHAR fullitem[MAX_PATH];
1483 /* Now build the item to use / search for in the specified directory,
1484 as it is fully qualified in the /R case */
1486 strcpyW(fullitem, dirsToWalk->dirName);
1487 strcatW(fullitem, slashW);
1488 strcatW(fullitem, item);
1490 strcpyW(fullitem, item);
1493 if (strpbrkW (fullitem, wildcards)) {
1495 hff = FindFirstFileW(fullitem, &fd);
1496 if (hff != INVALID_HANDLE_VALUE) {
1498 BOOL isDirectory = FALSE;
1500 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1502 /* Handle as files or dirs appropriately, but ignore . and .. */
1503 if (isDirectory == expandDirs &&
1504 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1505 (strcmpW(fd.cFileName, dotW) != 0))
1507 thisCmdStart = cmdStart;
1508 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1511 strcpyW(fullitem, dirsToWalk->dirName);
1512 strcatW(fullitem, slashW);
1513 strcatW(fullitem, fd.cFileName);
1515 strcpyW(fullitem, fd.cFileName);
1518 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1519 fullitem, FALSE, TRUE);
1520 cmdEnd = thisCmdStart;
1522 } while (FindNextFileW(hff, &fd) != 0);
1527 WCMD_part_execute(&thisCmdStart, firstCmd, variable, fullitem, FALSE, TRUE);
1528 cmdEnd = thisCmdStart;
1531 } else if (useNumbers) {
1532 /* Convert the first 3 numbers to signed longs and save */
1533 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1534 /* else ignore them! */
1536 /* Filesets - either a list of files, or a command to run and parse the output */
1537 } else if (doFileset && *itemStart != '"') {
1540 WCHAR temp_file[MAX_PATH];
1542 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1543 wine_dbgstr_w(item));
1545 /* If backquote or single quote, we need to launch that command
1546 and parse the results - use a temporary file */
1547 if (*itemStart == '`' || *itemStart == '\'') {
1549 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1550 static const WCHAR redirOut[] = {'>','%','s','\0'};
1551 static const WCHAR cmdW[] = {'C','M','D','\0'};
1553 /* Remove trailing character */
1554 itemStart[strlenW(itemStart)-1] = 0x00;
1556 /* Get temp filename */
1557 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1558 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1560 /* Execute program and redirect output */
1561 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1562 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1564 /* Open the file, read line by line and process */
1565 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1566 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1569 /* Open the file, read line by line and process */
1570 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1571 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1574 /* Process the input file */
1575 if (input == INVALID_HANDLE_VALUE) {
1576 WCMD_print_error ();
1577 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1579 return; /* FOR loop aborts at first failure here */
1583 WCHAR buffer[MAXSTRING];
1584 WCHAR *where, *parm;
1586 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1588 /* Skip blank lines*/
1589 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
1590 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1591 wine_dbgstr_w(buffer));
1594 /* FIXME: The following should be moved into its own routine and
1595 reused for the string literal parsing below */
1596 thisCmdStart = cmdStart;
1598 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1599 cmdEnd = thisCmdStart;
1605 CloseHandle (input);
1608 /* Delete the temporary file */
1609 if (*itemStart == '`' || *itemStart == '\'') {
1610 DeleteFileW(temp_file);
1613 /* Filesets - A string literal */
1614 } else if (doFileset && *itemStart == '"') {
1615 WCHAR buffer[MAXSTRING];
1616 WCHAR *where, *parm;
1618 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1619 strcpyW(buffer, item);
1620 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
1621 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1622 wine_dbgstr_w(buffer));
1625 /* FIXME: The following should be moved into its own routine and
1626 reused for the string literal parsing below */
1627 thisCmdStart = cmdStart;
1629 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1630 cmdEnd = thisCmdStart;
1634 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1638 /* Move onto the next set line */
1639 thisSet = thisSet->nextcommand;
1642 /* If /L is provided, now run the for loop */
1645 static const WCHAR fmt[] = {'%','d','\0'};
1647 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1648 numbers[0], numbers[2], numbers[1]);
1650 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
1653 sprintfW(thisNum, fmt, i);
1654 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1656 thisCmdStart = cmdStart;
1658 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1660 cmdEnd = thisCmdStart;
1663 /* If we are walking directories, move on to any which remain */
1664 if (dirsToWalk != NULL) {
1665 DIRECTORY_STACK *nextDir = dirsToWalk->next;
1666 HeapFree(GetProcessHeap(), 0, dirsToWalk->dirName);
1667 HeapFree(GetProcessHeap(), 0, dirsToWalk);
1668 dirsToWalk = nextDir;
1669 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
1670 wine_dbgstr_w(dirsToWalk->dirName));
1671 else WINE_TRACE("Finished all directories.\n");
1674 } while (dirsToWalk != NULL);
1676 /* Now skip over the do part if we did not perform the for loop so far.
1677 We store in cmdEnd the next command after the do block, but we only
1678 know this if something was run. If it has not been, we need to calculate
1681 thisCmdStart = cmdStart;
1682 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
1683 WCMD_part_execute(&thisCmdStart, firstCmd, NULL, NULL, FALSE, FALSE);
1684 cmdEnd = thisCmdStart;
1687 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1688 all processing, OR it should be pointing to the end of && processing OR
1689 it should be pointing at the NULL end of bracket for the DO. The return
1690 value needs to be the NEXT command to execute, which it either is, or
1691 we need to step over the closing bracket */
1693 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1696 /**************************************************************************
1699 * Simple on-line help. Help text is stored in the resource file.
1702 void WCMD_give_help (const WCHAR *command)
1706 command = WCMD_skip_leading_spaces((WCHAR*) command);
1707 if (strlenW(command) == 0) {
1708 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1711 /* Display help message for builtin commands */
1712 for (i=0; i<=WCMD_EXIT; i++) {
1713 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1714 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1715 WCMD_output_asis (WCMD_LoadMessage(i));
1719 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1720 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1721 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1722 command, -1, externals[i], -1) == CSTR_EQUAL) {
1724 static const WCHAR helpW[] = {' ', '/','?','\0'};
1725 strcpyW(cmd, command);
1726 strcatW(cmd, helpW);
1727 WCMD_run_program(cmd, FALSE);
1731 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1736 /****************************************************************************
1739 * Batch file jump instruction. Not the most efficient algorithm ;-)
1740 * Prints error message if the specified label cannot be found - the file pointer is
1741 * then at EOF, effectively stopping the batch file.
1742 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1745 void WCMD_goto (CMD_LIST **cmdList) {
1747 WCHAR string[MAX_PATH];
1748 WCHAR current[MAX_PATH];
1750 /* Do not process any more parts of a processed multipart or multilines command */
1751 if (cmdList) *cmdList = NULL;
1753 if (context != NULL) {
1754 WCHAR *paramStart = param1, *str;
1755 static const WCHAR eofW[] = {':','e','o','f','\0'};
1757 if (param1[0] == 0x00) {
1758 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1762 /* Handle special :EOF label */
1763 if (lstrcmpiW (eofW, param1) == 0) {
1764 context -> skip_rest = TRUE;
1768 /* Support goto :label as well as goto label */
1769 if (*paramStart == ':') paramStart++;
1771 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1772 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1774 while (isspaceW (*str)) str++;
1778 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1781 /* ignore space at the end */
1783 if (lstrcmpiW (current, paramStart) == 0) return;
1786 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1791 /*****************************************************************************
1794 * Push a directory onto the stack
1797 void WCMD_pushd (const WCHAR *command)
1799 struct env_stack *curdir;
1801 static const WCHAR parmD[] = {'/','D','\0'};
1803 if (strchrW(command, '/') != NULL) {
1804 SetLastError(ERROR_INVALID_PARAMETER);
1809 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1810 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1811 if( !curdir || !thisdir ) {
1814 WINE_ERR ("out of memory\n");
1818 /* Change directory using CD code with /D parameter */
1819 strcpyW(quals, parmD);
1820 GetCurrentDirectoryW (1024, thisdir);
1822 WCMD_setshow_default(command);
1828 curdir -> next = pushd_directories;
1829 curdir -> strings = thisdir;
1830 if (pushd_directories == NULL) {
1831 curdir -> u.stackdepth = 1;
1833 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1835 pushd_directories = curdir;
1840 /*****************************************************************************
1843 * Pop a directory from the stack
1846 void WCMD_popd (void) {
1847 struct env_stack *temp = pushd_directories;
1849 if (!pushd_directories)
1852 /* pop the old environment from the stack, and make it the current dir */
1853 pushd_directories = temp->next;
1854 SetCurrentDirectoryW(temp->strings);
1855 LocalFree (temp->strings);
1859 /****************************************************************************
1862 * Batch file conditional.
1864 * On entry, cmdlist will point to command containing the IF, and optionally
1865 * the first command to execute (if brackets not found)
1866 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1867 * If ('s were found, execute all within that bracket
1868 * Command may optionally be followed by an ELSE - need to skip instructions
1869 * in the else using the same logic
1871 * FIXME: Much more syntax checking needed!
1874 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1876 int negate; /* Negate condition */
1877 int test; /* Condition evaluation result */
1878 WCHAR condition[MAX_PATH], *command, *s;
1879 static const WCHAR notW[] = {'n','o','t','\0'};
1880 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1881 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1882 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1883 static const WCHAR eqeqW[] = {'=','=','\0'};
1884 static const WCHAR parmI[] = {'/','I','\0'};
1885 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1887 negate = !lstrcmpiW(param1,notW);
1888 strcpyW(condition, (negate ? param2 : param1));
1889 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1891 if (!lstrcmpiW (condition, errlvlW)) {
1892 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL, FALSE);
1894 long int param_int = strtolW(param, &endptr, 10);
1896 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1899 test = ((long int)errorlevel >= param_int);
1900 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1902 else if (!lstrcmpiW (condition, existW)) {
1903 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE))
1904 != INVALID_FILE_ATTRIBUTES);
1905 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1907 else if (!lstrcmpiW (condition, defdW)) {
1908 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE),
1910 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
1912 else if ((s = strstrW (p, eqeqW))) {
1913 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1914 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1916 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd, FALSE);
1917 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd, FALSE);
1918 test = caseInsensitive
1919 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1920 leftPart, leftPartEnd-leftPart+1,
1921 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1922 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1923 leftPart, leftPartEnd-leftPart+1,
1924 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1925 WCMD_parameter(s, 1, &command, NULL, FALSE);
1928 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1932 /* Process rest of IF statement which is on the same line
1933 Note: This may process all or some of the cmdList (eg a GOTO) */
1934 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1937 /****************************************************************************
1940 * Move a file, directory tree or wildcarded set of files.
1943 void WCMD_move (void)
1946 WIN32_FIND_DATAW fd;
1948 WCHAR input[MAX_PATH];
1949 WCHAR output[MAX_PATH];
1951 WCHAR dir[MAX_PATH];
1952 WCHAR fname[MAX_PATH];
1953 WCHAR ext[MAX_PATH];
1955 if (param1[0] == 0x00) {
1956 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1960 /* If no destination supplied, assume current directory */
1961 if (param2[0] == 0x00) {
1962 strcpyW(param2, dotW);
1965 /* If 2nd parm is directory, then use original filename */
1966 /* Convert partial path to full path */
1967 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1968 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1969 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1970 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1972 /* Split into components */
1973 WCMD_splitpath(input, drive, dir, fname, ext);
1975 hff = FindFirstFileW(input, &fd);
1976 if (hff == INVALID_HANDLE_VALUE)
1980 WCHAR dest[MAX_PATH];
1981 WCHAR src[MAX_PATH];
1985 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1987 /* Build src & dest name */
1988 strcpyW(src, drive);
1991 /* See if dest is an existing directory */
1992 attribs = GetFileAttributesW(output);
1993 if (attribs != INVALID_FILE_ATTRIBUTES &&
1994 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1995 strcpyW(dest, output);
1996 strcatW(dest, slashW);
1997 strcatW(dest, fd.cFileName);
1999 strcpyW(dest, output);
2002 strcatW(src, fd.cFileName);
2004 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2005 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2007 /* If destination exists, prompt unless /Y supplied */
2008 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2010 WCHAR copycmd[MAXSTRING];
2013 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2014 if (strstrW (quals, parmNoY))
2016 else if (strstrW (quals, parmY))
2019 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2020 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2021 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2022 && ! lstrcmpiW (copycmd, parmY));
2025 /* Prompt if overwriting */
2029 /* Ask for confirmation */
2030 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2031 ok = WCMD_ask_confirm(question, FALSE, NULL);
2032 LocalFree(question);
2034 /* So delete the destination prior to the move */
2036 if (!DeleteFileW(dest)) {
2037 WCMD_print_error ();
2046 status = MoveFileW(src, dest);
2048 status = 1; /* Anything other than 0 to prevent error msg below */
2052 WCMD_print_error ();
2055 } while (FindNextFileW(hff, &fd) != 0);
2060 /****************************************************************************
2063 * Suspend execution of a batch script until a key is typed
2066 void WCMD_pause (void)
2072 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2074 have_console = GetConsoleMode(hIn, &oldmode);
2076 SetConsoleMode(hIn, 0);
2078 WCMD_output_asis(anykey);
2079 WCMD_ReadFile(hIn, &key, 1, &count);
2081 SetConsoleMode(hIn, oldmode);
2084 /****************************************************************************
2087 * Delete a directory.
2090 void WCMD_remove_dir (WCHAR *command) {
2093 int argsProcessed = 0;
2094 WCHAR *argN = command;
2095 static const WCHAR parmS[] = {'/','S','\0'};
2096 static const WCHAR parmQ[] = {'/','Q','\0'};
2098 /* Loop through all args */
2100 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
2101 if (argN && argN[0] != '/') {
2102 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2103 wine_dbgstr_w(quals));
2106 /* If subdirectory search not supplied, just try to remove
2107 and report error if it fails (eg if it contains a file) */
2108 if (strstrW (quals, parmS) == NULL) {
2109 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2111 /* Otherwise use ShFileOp to recursively remove a directory */
2114 SHFILEOPSTRUCTW lpDir;
2117 if (strstrW (quals, parmQ) == NULL) {
2119 WCHAR question[MAXSTRING];
2120 static const WCHAR fmt[] = {'%','s',' ','\0'};
2122 /* Ask for confirmation */
2123 wsprintfW(question, fmt, thisArg);
2124 ok = WCMD_ask_confirm(question, TRUE, NULL);
2126 /* Abort if answer is 'N' */
2133 lpDir.pFrom = thisArg;
2134 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2135 lpDir.wFunc = FO_DELETE;
2137 /* SHFileOperationW needs file list with a double null termination */
2138 thisArg[lstrlenW(thisArg) + 1] = 0x00;
2140 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
2145 /* Handle no valid args */
2146 if (argsProcessed == 0) {
2147 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2153 /****************************************************************************
2159 void WCMD_rename (void)
2163 WIN32_FIND_DATAW fd;
2164 WCHAR input[MAX_PATH];
2165 WCHAR *dotDst = NULL;
2167 WCHAR dir[MAX_PATH];
2168 WCHAR fname[MAX_PATH];
2169 WCHAR ext[MAX_PATH];
2173 /* Must be at least two args */
2174 if (param1[0] == 0x00 || param2[0] == 0x00) {
2175 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2180 /* Destination cannot contain a drive letter or directory separator */
2181 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
2182 SetLastError(ERROR_INVALID_PARAMETER);
2188 /* Convert partial path to full path */
2189 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2190 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2191 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
2192 dotDst = strchrW(param2, '.');
2194 /* Split into components */
2195 WCMD_splitpath(input, drive, dir, fname, ext);
2197 hff = FindFirstFileW(input, &fd);
2198 if (hff == INVALID_HANDLE_VALUE)
2202 WCHAR dest[MAX_PATH];
2203 WCHAR src[MAX_PATH];
2204 WCHAR *dotSrc = NULL;
2207 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2209 /* FIXME: If dest name or extension is *, replace with filename/ext
2210 part otherwise use supplied name. This supports:
2212 ren jim.* fred.* etc
2213 However, windows has a more complex algorithm supporting eg
2214 ?'s and *'s mid name */
2215 dotSrc = strchrW(fd.cFileName, '.');
2217 /* Build src & dest name */
2218 strcpyW(src, drive);
2221 dirLen = strlenW(src);
2222 strcatW(src, fd.cFileName);
2225 if (param2[0] == '*') {
2226 strcatW(dest, fd.cFileName);
2227 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
2229 strcatW(dest, param2);
2230 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
2233 /* Build Extension */
2234 if (dotDst && (*(dotDst+1)=='*')) {
2235 if (dotSrc) strcatW(dest, dotSrc);
2236 } else if (dotDst) {
2237 if (dotDst) strcatW(dest, dotDst);
2240 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2241 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2243 status = MoveFileW(src, dest);
2246 WCMD_print_error ();
2249 } while (FindNextFileW(hff, &fd) != 0);
2254 /*****************************************************************************
2257 * Make a copy of the environment.
2259 static WCHAR *WCMD_dupenv( const WCHAR *env )
2269 len += (strlenW(&env[len]) + 1);
2271 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
2274 WINE_ERR("out of memory\n");
2277 memcpy (env_copy, env, len*sizeof (WCHAR));
2283 /*****************************************************************************
2286 * setlocal pushes the environment onto a stack
2287 * Save the environment as unicode so we don't screw anything up.
2289 void WCMD_setlocal (const WCHAR *s) {
2291 struct env_stack *env_copy;
2292 WCHAR cwd[MAX_PATH];
2294 /* setlocal does nothing outside of batch programs */
2295 if (!context) return;
2297 /* DISABLEEXTENSIONS ignored */
2299 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2302 WINE_ERR ("out of memory\n");
2306 env = GetEnvironmentStringsW ();
2307 env_copy->strings = WCMD_dupenv (env);
2308 if (env_copy->strings)
2310 env_copy->batchhandle = context->h;
2311 env_copy->next = saved_environment;
2312 saved_environment = env_copy;
2314 /* Save the current drive letter */
2315 GetCurrentDirectoryW(MAX_PATH, cwd);
2316 env_copy->u.cwd = cwd[0];
2319 LocalFree (env_copy);
2321 FreeEnvironmentStringsW (env);
2325 /*****************************************************************************
2328 * endlocal pops the environment off a stack
2329 * Note: When searching for '=', search from WCHAR position 1, to handle
2330 * special internal environment variables =C:, =D: etc
2332 void WCMD_endlocal (void) {
2333 WCHAR *env, *old, *p;
2334 struct env_stack *temp;
2337 /* setlocal does nothing outside of batch programs */
2338 if (!context) return;
2340 /* setlocal needs a saved environment from within the same context (batch
2341 program) as it was saved in */
2342 if (!saved_environment || saved_environment->batchhandle != context->h)
2345 /* pop the old environment from the stack */
2346 temp = saved_environment;
2347 saved_environment = temp->next;
2349 /* delete the current environment, totally */
2350 env = GetEnvironmentStringsW ();
2351 old = WCMD_dupenv (GetEnvironmentStringsW ());
2354 n = strlenW(&old[len]) + 1;
2355 p = strchrW(&old[len] + 1, '=');
2359 SetEnvironmentVariableW (&old[len], NULL);
2364 FreeEnvironmentStringsW (env);
2366 /* restore old environment */
2367 env = temp->strings;
2370 n = strlenW(&env[len]) + 1;
2371 p = strchrW(&env[len] + 1, '=');
2375 SetEnvironmentVariableW (&env[len], p);
2380 /* Restore current drive letter */
2381 if (IsCharAlphaW(temp->u.cwd)) {
2383 WCHAR cwd[MAX_PATH];
2384 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2386 wsprintfW(envvar, fmt, temp->u.cwd);
2387 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2388 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2389 SetCurrentDirectoryW(cwd);
2397 /*****************************************************************************
2398 * WCMD_setshow_default
2400 * Set/Show the current default directory
2403 void WCMD_setshow_default (const WCHAR *command) {
2409 WIN32_FIND_DATAW fd;
2411 static const WCHAR parmD[] = {'/','D','\0'};
2413 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2415 /* Skip /D and trailing whitespace if on the front of the command line */
2416 if (CompareStringW(LOCALE_USER_DEFAULT,
2417 NORM_IGNORECASE | SORT_STRINGSORT,
2418 command, 2, parmD, -1) == CSTR_EQUAL) {
2420 while (*command && (*command==' ' || *command=='\t'))
2424 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2425 if (strlenW(command) == 0) {
2426 strcatW (cwd, newlineW);
2427 WCMD_output_asis (cwd);
2430 /* Remove any double quotes, which may be in the
2431 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2434 if (*command != '"') *pos++ = *command;
2437 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2441 /* Search for appropriate directory */
2442 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2443 hff = FindFirstFileW(string, &fd);
2444 if (hff != INVALID_HANDLE_VALUE) {
2446 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2447 WCHAR fpath[MAX_PATH];
2449 WCHAR dir[MAX_PATH];
2450 WCHAR fname[MAX_PATH];
2451 WCHAR ext[MAX_PATH];
2452 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2454 /* Convert path into actual directory spec */
2455 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2456 WCMD_splitpath(fpath, drive, dir, fname, ext);
2459 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2462 } while (FindNextFileW(hff, &fd) != 0);
2466 /* Change to that directory */
2467 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2469 status = SetCurrentDirectoryW(string);
2472 WCMD_print_error ();
2476 /* Save away the actual new directory, to store as current location */
2477 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2479 /* Restore old directory if drive letter would change, and
2480 CD x:\directory /D (or pushd c:\directory) not supplied */
2481 if ((strstrW(quals, parmD) == NULL) &&
2482 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2483 SetCurrentDirectoryW(cwd);
2487 /* Set special =C: type environment variable, for drive letter of
2488 change of directory, even if path was restored due to missing
2489 /D (allows changing drive letter when not resident on that
2491 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2493 strcpyW(env, equalW);
2494 memcpy(env+1, string, 2 * sizeof(WCHAR));
2496 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2497 SetEnvironmentVariableW(env, string);
2504 /****************************************************************************
2507 * Set/Show the system date
2508 * FIXME: Can't change date yet
2511 void WCMD_setshow_date (void) {
2513 WCHAR curdate[64], buffer[64];
2515 static const WCHAR parmT[] = {'/','T','\0'};
2517 if (strlenW(param1) == 0) {
2518 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2519 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2520 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2521 if (strstrW (quals, parmT) == NULL) {
2522 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2523 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2525 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2529 else WCMD_print_error ();
2532 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2536 /****************************************************************************
2538 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
2541 static int WCMD_compare( const void *a, const void *b )
2544 const WCHAR * const *str_a = a, * const *str_b = b;
2545 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2546 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
2547 if( r == CSTR_LESS_THAN ) return -1;
2548 if( r == CSTR_GREATER_THAN ) return 1;
2552 /****************************************************************************
2553 * WCMD_setshow_sortenv
2555 * sort variables into order for display
2556 * Optionally only display those who start with a stub
2557 * returns the count displayed
2559 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2561 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2564 if (stub) stublen = strlenW(stub);
2566 /* count the number of strings, and the total length */
2568 len += (strlenW(&s[len]) + 1);
2572 /* add the strings to an array */
2573 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2577 for( i=1; i<count; i++ )
2578 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2580 /* sort the array */
2581 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2584 for( i=0; i<count; i++ ) {
2585 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2586 NORM_IGNORECASE | SORT_STRINGSORT,
2587 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2588 /* Don't display special internal variables */
2589 if (str[i][0] != '=') {
2590 WCMD_output_asis(str[i]);
2591 WCMD_output_asis(newlineW);
2598 return displayedcount;
2601 /****************************************************************************
2604 * Set/Show the environment variables
2607 void WCMD_setshow_env (WCHAR *s) {
2612 static const WCHAR parmP[] = {'/','P','\0'};
2614 if (param1[0] == 0x00 && quals[0] == 0x00) {
2615 env = GetEnvironmentStringsW();
2616 WCMD_setshow_sortenv( env, NULL );
2620 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2621 if (CompareStringW(LOCALE_USER_DEFAULT,
2622 NORM_IGNORECASE | SORT_STRINGSORT,
2623 s, 2, parmP, -1) == CSTR_EQUAL) {
2624 WCHAR string[MAXSTRING];
2628 while (*s && (*s==' ' || *s=='\t')) s++;
2630 WCMD_strip_quotes(s);
2632 /* If no parameter, or no '=' sign, return an error */
2633 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2634 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2638 /* Output the prompt */
2640 if (strlenW(p) != 0) WCMD_output_asis(p);
2642 /* Read the reply */
2643 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2645 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2646 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2647 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2648 wine_dbgstr_w(string));
2649 status = SetEnvironmentVariableW(s, string);
2656 WCMD_strip_quotes(s);
2657 p = strchrW (s, '=');
2659 env = GetEnvironmentStringsW();
2660 if (WCMD_setshow_sortenv( env, s ) == 0) {
2661 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2668 if (strlenW(p) == 0) p = NULL;
2669 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2671 status = SetEnvironmentVariableW(s, p);
2672 gle = GetLastError();
2673 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2675 } else if ((!status)) WCMD_print_error();
2676 else errorlevel = 0;
2680 /****************************************************************************
2683 * Set/Show the path environment variable
2686 void WCMD_setshow_path (const WCHAR *command) {
2690 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2691 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2693 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
2694 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2696 WCMD_output_asis ( pathEqW);
2697 WCMD_output_asis ( string);
2698 WCMD_output_asis ( newlineW);
2701 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2705 if (*command == '=') command++; /* Skip leading '=' */
2706 status = SetEnvironmentVariableW(pathW, command);
2707 if (!status) WCMD_print_error();
2711 /****************************************************************************
2712 * WCMD_setshow_prompt
2714 * Set or show the command prompt.
2717 void WCMD_setshow_prompt (void) {
2720 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2722 if (strlenW(param1) == 0) {
2723 SetEnvironmentVariableW(promptW, NULL);
2727 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2728 if (strlenW(s) == 0) {
2729 SetEnvironmentVariableW(promptW, NULL);
2731 else SetEnvironmentVariableW(promptW, s);
2735 /****************************************************************************
2738 * Set/Show the system time
2739 * FIXME: Can't change time yet
2742 void WCMD_setshow_time (void) {
2744 WCHAR curtime[64], buffer[64];
2747 static const WCHAR parmT[] = {'/','T','\0'};
2749 if (strlenW(param1) == 0) {
2751 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2752 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2753 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2754 if (strstrW (quals, parmT) == NULL) {
2755 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2756 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2758 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2762 else WCMD_print_error ();
2765 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2769 /****************************************************************************
2772 * Shift batch parameters.
2773 * Optional /n says where to start shifting (n=0-8)
2776 void WCMD_shift (const WCHAR *command) {
2779 if (context != NULL) {
2780 WCHAR *pos = strchrW(command, '/');
2785 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2786 start = (*(pos+1) - '0');
2788 SetLastError(ERROR_INVALID_PARAMETER);
2793 WINE_TRACE("Shifting variables, starting at %d\n", start);
2794 for (i=start;i<=8;i++) {
2795 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2797 context -> shift_count[9] = context -> shift_count[9] + 1;
2802 /****************************************************************************
2805 void WCMD_start(const WCHAR *command)
2807 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
2808 '\\','s','t','a','r','t','.','e','x','e',0};
2809 WCHAR file[MAX_PATH];
2812 PROCESS_INFORMATION pi;
2814 GetWindowsDirectoryW( file, MAX_PATH );
2815 strcatW( file, exeW );
2816 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
2817 strcpyW( cmdline, file );
2818 strcatW( cmdline, spaceW );
2819 strcatW( cmdline, command );
2821 memset( &st, 0, sizeof(STARTUPINFOW) );
2822 st.cb = sizeof(STARTUPINFOW);
2824 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
2826 WaitForSingleObject( pi.hProcess, INFINITE );
2827 GetExitCodeProcess( pi.hProcess, &errorlevel );
2828 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
2829 CloseHandle(pi.hProcess);
2830 CloseHandle(pi.hThread);
2834 SetLastError(ERROR_FILE_NOT_FOUND);
2835 WCMD_print_error ();
2838 HeapFree( GetProcessHeap(), 0, cmdline );
2841 /****************************************************************************
2844 * Set the console title
2846 void WCMD_title (const WCHAR *command) {
2847 SetConsoleTitleW(command);
2850 /****************************************************************************
2853 * Copy a file to standard output.
2856 void WCMD_type (WCHAR *command) {
2859 WCHAR *argN = command;
2860 BOOL writeHeaders = FALSE;
2862 if (param1[0] == 0x00) {
2863 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2867 if (param2[0] != 0x00) writeHeaders = TRUE;
2869 /* Loop through all args */
2872 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
2880 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2881 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2882 FILE_ATTRIBUTE_NORMAL, NULL);
2883 if (h == INVALID_HANDLE_VALUE) {
2884 WCMD_print_error ();
2885 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2889 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2890 WCMD_output(fmt, thisArg);
2892 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2893 if (count == 0) break; /* ReadFile reports success on EOF! */
2895 WCMD_output_asis (buffer);
2902 /****************************************************************************
2905 * Output either a file or stdin to screen in pages
2908 void WCMD_more (WCHAR *command) {
2911 WCHAR *argN = command;
2913 WCHAR moreStrPage[100];
2916 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2917 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2918 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2919 ')',' ','-','-','\n','\0'};
2920 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2922 /* Prefix the NLS more with '-- ', then load the text */
2924 strcpyW(moreStr, moreStart);
2925 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2926 (sizeof(moreStr)/sizeof(WCHAR))-3);
2928 if (param1[0] == 0x00) {
2930 /* Wine implements pipes via temporary files, and hence stdin is
2931 effectively reading from the file. This means the prompts for
2932 more are satisfied by the next line from the input (file). To
2933 avoid this, ensure stdin is to the console */
2934 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2935 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2936 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2937 FILE_ATTRIBUTE_NORMAL, 0);
2938 WINE_TRACE("No parms - working probably in pipe mode\n");
2939 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2941 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2942 once you get in this bit unless due to a pipe, its going to end badly... */
2943 wsprintfW(moreStrPage, moreFmt, moreStr);
2945 WCMD_enter_paged_mode(moreStrPage);
2946 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2947 if (count == 0) break; /* ReadFile reports success on EOF! */
2949 WCMD_output_asis (buffer);
2951 WCMD_leave_paged_mode();
2953 /* Restore stdin to what it was */
2954 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2955 CloseHandle(hConIn);
2959 BOOL needsPause = FALSE;
2961 /* Loop through all args */
2962 WINE_TRACE("Parms supplied - working through each file\n");
2963 WCMD_enter_paged_mode(moreStrPage);
2966 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
2974 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2975 WCMD_leave_paged_mode();
2976 WCMD_output_asis(moreStrPage);
2977 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2978 WCMD_enter_paged_mode(moreStrPage);
2982 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2983 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2984 FILE_ATTRIBUTE_NORMAL, NULL);
2985 if (h == INVALID_HANDLE_VALUE) {
2986 WCMD_print_error ();
2987 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2991 ULONG64 fileLen = 0;
2992 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2994 /* Get the file size */
2995 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2996 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2999 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3000 if (count == 0) break; /* ReadFile reports success on EOF! */
3004 /* Update % count (would be used in WCMD_output_asis as prompt) */
3005 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
3007 WCMD_output_asis (buffer);
3013 WCMD_leave_paged_mode();
3017 /****************************************************************************
3020 * Display verify flag.
3021 * FIXME: We don't actually do anything with the verify flag other than toggle
3025 void WCMD_verify (const WCHAR *command) {
3029 count = strlenW(command);
3031 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
3032 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
3035 if (lstrcmpiW(command, onW) == 0) {
3039 else if (lstrcmpiW(command, offW) == 0) {
3040 verify_mode = FALSE;
3043 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
3046 /****************************************************************************
3049 * Display version info.
3052 void WCMD_version (void) {
3054 WCMD_output_asis (version_string);
3058 /****************************************************************************
3061 * Display volume information (set_label = FALSE)
3062 * Additionally set volume label (set_label = TRUE)
3063 * Returns 1 on success, 0 otherwise
3066 int WCMD_volume(BOOL set_label, const WCHAR *path)
3068 DWORD count, serial;
3069 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
3072 if (strlenW(path) == 0) {
3073 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
3075 WCMD_print_error ();
3078 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
3079 &serial, NULL, NULL, NULL, 0);
3082 static const WCHAR fmt[] = {'%','s','\\','\0'};
3083 if ((path[1] != ':') || (strlenW(path) != 2)) {
3084 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
3087 wsprintfW (curdir, fmt, path);
3088 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
3093 WCMD_print_error ();
3096 if (label[0] != '\0') {
3097 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
3101 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
3104 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
3105 HIWORD(serial), LOWORD(serial));
3107 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
3108 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3110 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3111 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3113 if (strlenW(path) != 0) {
3114 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
3117 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
3123 /**************************************************************************
3126 * Exit either the process, or just this batch program
3130 void WCMD_exit (CMD_LIST **cmdList) {
3132 static const WCHAR parmB[] = {'/','B','\0'};
3133 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
3135 if (context && lstrcmpiW(quals, parmB) == 0) {
3137 context -> skip_rest = TRUE;
3145 /*****************************************************************************
3148 * Lists or sets file associations (assoc = TRUE)
3149 * Lists or sets file types (assoc = FALSE)
3151 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
3154 DWORD accessOptions = KEY_READ;
3156 LONG rc = ERROR_SUCCESS;
3157 WCHAR keyValue[MAXSTRING];
3158 DWORD valueLen = MAXSTRING;
3160 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
3161 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
3163 /* See if parameter includes '=' */
3165 newValue = strchrW(command, '=');
3166 if (newValue) accessOptions |= KEY_WRITE;
3168 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
3169 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
3170 accessOptions, &key) != ERROR_SUCCESS) {
3171 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
3175 /* If no parameters then list all associations */
3176 if (*command == 0x00) {
3179 /* Enumerate all the keys */
3180 while (rc != ERROR_NO_MORE_ITEMS) {
3181 WCHAR keyName[MAXSTRING];
3184 /* Find the next value */
3185 nameLen = MAXSTRING;
3186 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
3188 if (rc == ERROR_SUCCESS) {
3190 /* Only interested in extension ones if assoc, or others
3192 if ((keyName[0] == '.' && assoc) ||
3193 (!(keyName[0] == '.') && (!assoc)))
3195 WCHAR subkey[MAXSTRING];
3196 strcpyW(subkey, keyName);
3197 if (!assoc) strcatW(subkey, shOpCmdW);
3199 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3201 valueLen = sizeof(keyValue)/sizeof(WCHAR);
3202 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3203 WCMD_output_asis(keyName);
3204 WCMD_output_asis(equalW);
3205 /* If no default value found, leave line empty after '=' */
3206 if (rc == ERROR_SUCCESS) {
3207 WCMD_output_asis(keyValue);
3209 WCMD_output_asis(newlineW);
3210 RegCloseKey(readKey);
3218 /* Parameter supplied - if no '=' on command line, its a query */
3219 if (newValue == NULL) {
3221 WCHAR subkey[MAXSTRING];
3223 /* Query terminates the parameter at the first space */
3224 strcpyW(keyValue, command);
3225 space = strchrW(keyValue, ' ');
3226 if (space) *space=0x00;
3228 /* Set up key name */
3229 strcpyW(subkey, keyValue);
3230 if (!assoc) strcatW(subkey, shOpCmdW);
3232 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3234 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3235 WCMD_output_asis(command);
3236 WCMD_output_asis(equalW);
3237 /* If no default value found, leave line empty after '=' */
3238 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
3239 WCMD_output_asis(newlineW);
3240 RegCloseKey(readKey);
3243 WCHAR msgbuffer[MAXSTRING];
3245 /* Load the translated 'File association not found' */
3247 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3249 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3251 WCMD_output_stderr(msgbuffer, keyValue);
3255 /* Not a query - its a set or clear of a value */
3258 WCHAR subkey[MAXSTRING];
3260 /* Get pointer to new value */
3264 /* Set up key name */
3265 strcpyW(subkey, command);
3266 if (!assoc) strcatW(subkey, shOpCmdW);
3268 /* If nothing after '=' then clear value - only valid for ASSOC */
3269 if (*newValue == 0x00) {
3271 if (assoc) rc = RegDeleteKeyW(key, command);
3272 if (assoc && rc == ERROR_SUCCESS) {
3273 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
3275 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
3280 WCHAR msgbuffer[MAXSTRING];
3282 /* Load the translated 'File association not found' */
3284 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
3285 sizeof(msgbuffer)/sizeof(WCHAR));
3287 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
3288 sizeof(msgbuffer)/sizeof(WCHAR));
3290 WCMD_output_stderr(msgbuffer, keyValue);
3294 /* It really is a set value = contents */
3296 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
3297 accessOptions, NULL, &readKey, NULL);
3298 if (rc == ERROR_SUCCESS) {
3299 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
3301 sizeof(WCHAR) * (strlenW(newValue) + 1));
3302 RegCloseKey(readKey);
3305 if (rc != ERROR_SUCCESS) {
3309 WCMD_output_asis(command);
3310 WCMD_output_asis(equalW);
3311 WCMD_output_asis(newValue);
3312 WCMD_output_asis(newlineW);
3322 /****************************************************************************
3325 * Colors the terminal screen.
3328 void WCMD_color (void) {
3330 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3331 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3333 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3334 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3338 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3344 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3349 /* Convert the color hex digits */
3350 if (param1[0] == 0x00) {
3351 color = defaultColor;
3353 color = strtoulW(param1, NULL, 16);
3356 /* Fail if fg == bg color */
3357 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3362 /* Set the current screen contents and ensure all future writes
3363 remain this color */
3364 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3365 SetConsoleTextAttribute(hStdOut, color);