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 wildcardsW[] = {'*','?','\0'};
49 const WCHAR slashstarW[] = {'\\','*','\0'};
50 const WCHAR inbuilt[][10] = {
51 {'C','A','L','L','\0'},
53 {'C','H','D','I','R','\0'},
55 {'C','O','P','Y','\0'},
56 {'C','T','T','Y','\0'},
57 {'D','A','T','E','\0'},
60 {'E','C','H','O','\0'},
61 {'E','R','A','S','E','\0'},
63 {'G','O','T','O','\0'},
64 {'H','E','L','P','\0'},
66 {'L','A','B','E','L','\0'},
68 {'M','K','D','I','R','\0'},
69 {'M','O','V','E','\0'},
70 {'P','A','T','H','\0'},
71 {'P','A','U','S','E','\0'},
72 {'P','R','O','M','P','T','\0'},
75 {'R','E','N','A','M','E','\0'},
77 {'R','M','D','I','R','\0'},
79 {'S','H','I','F','T','\0'},
80 {'S','T','A','R','T','\0'},
81 {'T','I','M','E','\0'},
82 {'T','I','T','L','E','\0'},
83 {'T','Y','P','E','\0'},
84 {'V','E','R','I','F','Y','\0'},
87 {'E','N','D','L','O','C','A','L','\0'},
88 {'S','E','T','L','O','C','A','L','\0'},
89 {'P','U','S','H','D','\0'},
90 {'P','O','P','D','\0'},
91 {'A','S','S','O','C','\0'},
92 {'C','O','L','O','R','\0'},
93 {'F','T','Y','P','E','\0'},
94 {'M','O','R','E','\0'},
95 {'C','H','O','I','C','E','\0'},
96 {'E','X','I','T','\0'}
98 static const WCHAR externals[][10] = {
99 {'A','T','T','R','I','B','\0'},
100 {'X','C','O','P','Y','\0'}
102 static const WCHAR fslashW[] = {'/','\0'};
103 static const WCHAR onW[] = {'O','N','\0'};
104 static const WCHAR offW[] = {'O','F','F','\0'};
105 static const WCHAR parmY[] = {'/','Y','\0'};
106 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
107 static const WCHAR eqeqW[] = {'=','=','\0'};
109 static HINSTANCE hinst;
110 struct env_stack *saved_environment;
111 static BOOL verify_mode = FALSE;
113 /**************************************************************************
116 * Issue a message and ask for confirmation, waiting on a valid answer.
118 * Returns True if Y (or A) answer is selected
119 * If optionAll contains a pointer, ALL is allowed, and if answered
123 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
127 WCHAR confirm[MAXSTRING];
128 WCHAR options[MAXSTRING];
129 WCHAR Ybuffer[MAXSTRING];
130 WCHAR Nbuffer[MAXSTRING];
131 WCHAR Abuffer[MAXSTRING];
132 WCHAR answer[MAX_PATH] = {'\0'};
135 /* Load the translated valid answers */
137 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
138 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
139 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
140 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
141 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
142 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
144 /* Loop waiting on a valid answer */
149 WCMD_output_asis (message);
151 WCMD_output_asis (confirm);
152 WCMD_output_asis (options);
153 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
154 answer[0] = toupperW(answer[0]);
155 if (answer[0] == Ybuffer[0])
157 if (answer[0] == Nbuffer[0])
159 if (optionAll && answer[0] == Abuffer[0])
167 /****************************************************************************
170 * Clear the terminal screen.
173 void WCMD_clear_screen (void) {
175 /* Emulate by filling the screen from the top left to bottom right with
176 spaces, then moving the cursor to the top left afterwards */
177 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
178 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
180 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
185 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
189 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
190 SetConsoleCursorPosition(hStdOut, topLeft);
194 /****************************************************************************
197 * Change the default i/o device (ie redirect STDin/STDout).
200 void WCMD_change_tty (void) {
202 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
206 /****************************************************************************
211 void WCMD_choice (const WCHAR * args) {
213 static const WCHAR bellW[] = {7,0};
214 static const WCHAR commaW[] = {',',0};
215 static const WCHAR bracket_open[] = {'[',0};
216 static const WCHAR bracket_close[] = {']','?',0};
221 WCHAR *my_command = NULL;
222 WCHAR opt_default = 0;
223 DWORD opt_timeout = 0;
230 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
233 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
237 ptr = WCMD_skip_leading_spaces(my_command);
238 while (*ptr == '/') {
239 switch (toupperW(ptr[1])) {
242 /* the colon is optional */
246 if (!*ptr || isspaceW(*ptr)) {
247 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
248 HeapFree(GetProcessHeap(), 0, my_command);
252 /* remember the allowed keys (overwrite previous /C option) */
254 while (*ptr && (!isspaceW(*ptr)))
258 /* terminate allowed chars */
260 ptr = WCMD_skip_leading_spaces(&ptr[1]);
262 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
267 ptr = WCMD_skip_leading_spaces(&ptr[2]);
272 ptr = WCMD_skip_leading_spaces(&ptr[2]);
277 /* the colon is optional */
281 opt_default = *ptr++;
283 if (!opt_default || (*ptr != ',')) {
284 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
285 HeapFree(GetProcessHeap(), 0, my_command);
291 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
297 opt_timeout = atoiW(answer);
299 ptr = WCMD_skip_leading_spaces(ptr);
303 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
304 HeapFree(GetProcessHeap(), 0, my_command);
310 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
313 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
315 /* use default keys, when needed: localized versions of "Y"es and "No" */
317 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
318 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
323 /* print the question, when needed */
325 WCMD_output_asis(ptr);
329 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
333 /* print a list of all allowed answers inside brackets */
334 WCMD_output_asis(bracket_open);
337 while ((answer[0] = *ptr++)) {
338 WCMD_output_asis(answer);
340 WCMD_output_asis(commaW);
342 WCMD_output_asis(bracket_close);
347 /* FIXME: Add support for option /T */
348 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
351 answer[0] = toupperW(answer[0]);
353 ptr = strchrW(opt_c, answer[0]);
355 WCMD_output_asis(answer);
356 WCMD_output_asis(newlineW);
358 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
360 errorlevel = (ptr - opt_c) + 1;
361 WINE_TRACE("answer: %d\n", errorlevel);
362 HeapFree(GetProcessHeap(), 0, my_command);
367 /* key not allowed: play the bell */
368 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
369 WCMD_output_asis(bellW);
374 /****************************************************************************
377 * Adds an EOF onto the end of a file
378 * Returns TRUE on success
380 static BOOL WCMD_AppendEOF(WCHAR *filename)
386 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
387 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
388 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
391 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
394 SetFilePointer (h, 0, NULL, FILE_END);
395 if (!WriteFile(h, &eof, 1, NULL, NULL)) {
396 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
405 /****************************************************************************
409 * optionally reading only until EOF (ascii copy)
410 * optionally appending onto an existing file (append)
411 * Returns TRUE on success
413 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
417 DWORD bytesread, byteswritten;
419 WINE_TRACE("ASCII Copying %s to %s (append?%d)\n",
420 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
422 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
423 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
425 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
429 /* Open the output file, overwriting if not appending */
430 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
431 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
433 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
438 /* Move to end of destination if we are going to append to it */
440 SetFilePointer(out, 0, NULL, FILE_END);
443 /* Loop copying data from source to destination until EOF read */
447 char buffer[MAXSTRING];
449 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
452 /* Stop at first EOF */
454 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
455 if (ptr) bytesread = (ptr - buffer);
459 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
460 if (!ok || byteswritten != bytesread) {
461 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
462 wine_dbgstr_w(dstname), GetLastError());
466 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
467 wine_dbgstr_w(srcname), GetLastError());
469 } while (ok && bytesread > 0);
476 /****************************************************************************
479 * Copy a file or wildcarded set.
480 * For ascii/binary type copies, it gets complex:
481 * Syntax on command line is
482 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
483 * Where first /a or /b sets 'mode in operation' until another is found
484 * once another is found, it applies to the file preceding the /a or /b
485 * In addition each filename can contain wildcards
486 * To make matters worse, the + may be in the same parameter (i.e. no
487 * whitespace) or with whitespace separating it
489 * ASCII mode on read == read and stop at first EOF
490 * ASCII mode on write == append EOF to destination
491 * Binary == copy as-is
493 * Design of this is to build up a list of files which will be copied into a
494 * list, then work through the list file by file.
495 * If no destination is specified, it defaults to the name of the first file in
496 * the list, but the current directory.
500 void WCMD_copy(WCHAR * args) {
502 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
508 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
509 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
510 BOOL anyconcats = FALSE; /* Have we found any + options */
511 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
512 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
513 BOOL prompt; /* Prompt before overwriting */
514 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
515 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
519 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
521 typedef struct _COPY_FILES
523 struct _COPY_FILES *next;
528 COPY_FILES *sourcelist = NULL;
529 COPY_FILES *lastcopyentry = NULL;
530 COPY_FILES *destination = NULL;
531 COPY_FILES *thiscopy = NULL;
532 COPY_FILES *prevcopy = NULL;
534 /* Assume we were successful! */
537 /* If no args supplied at all, report an error */
538 if (param1[0] == 0x00) {
539 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
544 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
546 /* Walk through all args, building up a list of files to process */
547 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
548 while (*(thisparam)) {
552 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
554 /* Handle switches */
555 if (*thisparam == '/') {
556 while (*thisparam == '/') {
558 if (toupperW(*thisparam) == 'D') {
560 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
561 } else if (toupperW(*thisparam) == 'Y') {
563 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
565 } else if (toupperW(*thisparam) == 'V') {
567 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
568 } else if (toupperW(*thisparam) == 'N') {
570 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
571 } else if (toupperW(*thisparam) == 'Z') {
573 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
574 } else if (toupperW(*thisparam) == 'A') {
575 if (binarymode != 0) {
577 WINE_TRACE("Subsequent files will be handled as ASCII\n");
578 if (destination != NULL) {
579 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
580 destination->binarycopy = binarymode;
581 } else if (lastcopyentry != NULL) {
582 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
583 lastcopyentry->binarycopy = binarymode;
586 } else if (toupperW(*thisparam) == 'B') {
587 if (binarymode != 1) {
589 WINE_TRACE("Subsequent files will be handled as binary\n");
590 if (destination != NULL) {
591 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
592 destination->binarycopy = binarymode;
593 } else if (lastcopyentry != NULL) {
594 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
595 lastcopyentry->binarycopy = binarymode;
599 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
604 /* This parameter was purely switches, get the next one */
605 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
609 /* We have found something which is not a switch. If could be anything of the form
610 sourcefilename (which could be destination too)
611 + (when filename + filename syntex used)
612 sourcefilename+sourcefilename
614 +/b[tests show windows then ignores to end of parameter]
617 if (*thisparam=='+') {
618 if (lastcopyentry == NULL) {
619 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
623 concatnextfilename = TRUE;
627 /* Move to next thing to process */
629 if (*thisparam == 0x00)
630 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
634 /* We have found something to process - build a COPY_FILE block to store it */
635 thiscopy = HeapAlloc(GetProcessHeap(),0,sizeof(COPY_FILES));
636 if (thiscopy == NULL) goto exitreturn;
639 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
640 thiscopy->concatenate = concatnextfilename;
641 thiscopy->binarycopy = binarymode;
642 thiscopy->next = NULL;
644 /* Time to work out the name. Allocate at least enough space (deliberately too much to
645 leave space to append \* to the end) , then copy in character by character. Strip off
646 quotes if we find them. */
647 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
648 thiscopy->name = HeapAlloc(GetProcessHeap(),0,len*sizeof(WCHAR));
649 memset(thiscopy->name, 0x00, len);
652 pos2 = thiscopy->name;
654 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
656 inquotes = !inquotes;
658 } else *pos2++ = *pos1++;
661 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
663 /* This is either the first source, concatenated subsequent source or destination */
664 if (sourcelist == NULL) {
665 WINE_TRACE("Adding as first source part\n");
666 sourcelist = thiscopy;
667 lastcopyentry = thiscopy;
668 } else if (concatnextfilename) {
669 WINE_TRACE("Adding to source file list to be concatenated\n");
670 lastcopyentry->next = thiscopy;
671 lastcopyentry = thiscopy;
672 } else if (destination == NULL) {
673 destination = thiscopy;
675 /* We have processed sources and destinations and still found more to do - invalid */
676 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
680 concatnextfilename = FALSE;
682 /* We either need to process the rest of the parameter or move to the next */
683 if (*pos1 == '/' || *pos1 == '+') {
687 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
691 /* Ensure we have at least one source file */
693 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
698 /* Default whether automatic overwriting is on. If we are interactive then
699 we prompt by default, otherwise we overwrite by default
700 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
701 if (opt_noty) prompt = TRUE;
702 else if (opt_y) prompt = FALSE;
704 /* By default, we will force the overwrite in batch mode and ask for
705 * confirmation in interactive mode. */
706 prompt = interactive;
707 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
708 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
709 * default behavior. */
710 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
711 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
712 if (!lstrcmpiW (copycmd, parmY))
714 else if (!lstrcmpiW (copycmd, parmNoY))
719 /* Calculate the destination now - if none supplied, its current dir +
720 filename of first file in list*/
721 if (destination == NULL) {
723 WINE_TRACE("No destination supplied, so need to calculate it\n");
724 strcpyW(destname, dotW);
725 strcatW(destname, slashW);
727 destination = HeapAlloc(GetProcessHeap(),0,sizeof(COPY_FILES));
728 if (destination == NULL) goto exitreturn;
729 destination->concatenate = FALSE; /* Not used for destination */
730 destination->binarycopy = binarymode;
731 destination->next = NULL; /* Not used for destination */
732 destination->name = NULL; /* To be filled in */
733 destisdirectory = TRUE;
739 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
741 /* Convert to fully qualified path/filename */
742 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
743 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
745 /* If parameter is a directory, ensure it ends in \ */
746 attributes = GetFileAttributesW(destname);
747 if ((destname[strlenW(destname) - 1] == '\\') ||
748 ((attributes != INVALID_FILE_ATTRIBUTES) &&
749 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
751 destisdirectory = TRUE;
752 if (!(destname[strlenW(destname) - 1] == '\\')) strcatW(destname, slashW);
753 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
757 /* Normally, the destination is the current directory unless we are
758 concatenating, in which case its current directory plus first filename.
760 In addition by default it is a binary copy unless concatenating, when
761 the copy defaults to an ascii copy (stop at EOF). We do not know the
762 first source part yet (until we search) so flag as needing filling in. */
765 /* We have found an a+b type syntax, so destination has to be a filename
766 and we need to default to ascii copying. If we have been supplied a
767 directory as the destination, we need to defer calculating the name */
768 if (destisdirectory) appendfirstsource = TRUE;
769 if (destination->binarycopy == -1) destination->binarycopy = 0;
771 } else if (!destisdirectory) {
772 /* We have been asked to copy to a filename. Default to ascii IF the
773 source contains wildcards (true even if only one match) */
774 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
775 anyconcats = TRUE; /* We really are concatenating to a single file */
776 if (destination->binarycopy == -1) {
777 destination->binarycopy = 0;
780 if (destination->binarycopy == -1) {
781 destination->binarycopy = 1;
786 /* Save away the destination name*/
787 HeapFree(GetProcessHeap(), 0, destination->name);
788 destination->name = WCMD_strdupW(destname);
789 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
790 wine_dbgstr_w(destname), appendfirstsource);
792 /* Now we need to walk the set of sources, and process each name we come to.
793 If anyconcats is true, we are writing to one file, otherwise we are using
794 the source name each time.
795 If destination exists, prompt for overwrite the first time (if concatenating
796 we ask each time until yes is answered)
797 The first source file we come across must exist (when wildcards expanded)
798 and if concatenating with overwrite prompts, each source file must exist
799 until a yes is answered. */
801 thiscopy = sourcelist;
804 while (thiscopy != NULL) {
806 WCHAR srcpath[MAX_PATH];
810 /* If it was not explicit, we now know whether we are concatenating or not and
811 hence whether to copy as binary or ascii */
812 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
814 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
815 to where the filename portion begins (used for wildcart expansion. */
816 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
817 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
819 /* If parameter is a directory, ensure it ends in \* */
820 attributes = GetFileAttributesW(srcpath);
821 if (srcpath[strlenW(srcpath) - 1] == '\\') {
823 /* We need to know where the filename part starts, so append * and
824 recalculate the full resulting path */
825 strcatW(thiscopy->name, starW);
826 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
827 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
829 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
830 (attributes != INVALID_FILE_ATTRIBUTES) &&
831 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
833 /* We need to know where the filename part starts, so append \* and
834 recalculate the full resulting path */
835 strcatW(thiscopy->name, slashstarW);
836 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
837 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
840 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
841 wine_dbgstr_w(srcpath), anyconcats);
843 /* Loop through all source files */
844 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
845 hff = FindFirstFileW(srcpath, &fd);
846 if (hff != INVALID_HANDLE_VALUE) {
848 WCHAR outname[MAX_PATH];
851 /* Skip . and .., and directories */
852 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
853 WINE_TRACE("Skipping directories\n");
856 /* Build final destination name */
857 strcpyW(outname, destination->name);
858 if (destisdirectory || appendfirstsource) strcatW(outname, fd.cFileName);
860 /* Build source name */
861 strcpyW(filenamepart, fd.cFileName);
863 /* Do we just overwrite */
865 if (anyconcats && writtenoneconcat) {
869 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
870 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
871 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
872 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
874 /* Prompt before overwriting */
876 DWORD attributes = GetFileAttributesW(outname);
877 if (attributes != INVALID_FILE_ATTRIBUTES) {
879 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
880 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
883 else overwrite = TRUE;
886 /* If we needed tyo save away the first filename, do it */
887 if (appendfirstsource && overwrite) {
888 HeapFree(GetProcessHeap(), 0, destination->name);
889 destination->name = WCMD_strdupW(outname);
890 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
891 appendfirstsource = FALSE;
892 destisdirectory = FALSE;
895 /* Do the copy as appropriate */
897 if (anyconcats && writtenoneconcat) {
898 if (thiscopy->binarycopy) {
899 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
901 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
903 } else if (!thiscopy->binarycopy) {
904 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
906 status = CopyFileW(srcpath, outname, FALSE);
912 WINE_TRACE("Copied successfully\n");
913 if (anyconcats) writtenoneconcat = TRUE;
915 /* Append EOF if ascii destination and we are not going to add more onto the end
916 Note: Testing shows windows has an optimization whereas if you have a binary
917 copy of a file to a single destination (ie concatenation) then it does not add
918 the EOF, hence the check on the source copy type below. */
919 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
920 if (!WCMD_AppendEOF(outname)) {
928 } while (FindNextFileW(hff, &fd) != 0);
931 /* Error if the first file was not found */
932 if (!anyconcats || (anyconcats && !writtenoneconcat)) {
938 /* Step on to the next supplied source */
939 thiscopy = thiscopy -> next;
942 /* Append EOF if ascii destination and we were concatenating */
943 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
944 if (!WCMD_AppendEOF(destination->name)) {
950 /* Exit out of the routine, freeing any remaining allocated memory */
953 thiscopy = sourcelist;
954 while (thiscopy != NULL) {
956 /* Free up this block*/
957 thiscopy = thiscopy -> next;
958 HeapFree(GetProcessHeap(), 0, prevcopy->name);
959 HeapFree(GetProcessHeap(), 0, prevcopy);
962 /* Free up the destination memory */
964 HeapFree(GetProcessHeap(), 0, destination->name);
965 HeapFree(GetProcessHeap(), 0, destination);
971 /****************************************************************************
974 * Create a directory (and, if needed, any intermediate directories).
976 * Modifies its argument by replacing slashes temporarily with nulls.
979 static BOOL create_full_path(WCHAR* path)
983 /* don't mess with drive letter portion of path, if any */
988 /* Strip trailing slashes. */
989 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
992 /* Step through path, creating intermediate directories as needed. */
993 /* First component includes drive letter, if any. */
997 /* Skip to end of component */
998 while (*p == '\\') p++;
999 while (*p && *p != '\\') p++;
1001 /* path is now the original full path */
1002 return CreateDirectoryW(path, NULL);
1004 /* Truncate path, create intermediate directory, and restore path */
1006 rv = CreateDirectoryW(path, NULL);
1008 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1015 void WCMD_create_dir (WCHAR *args) {
1019 if (param1[0] == 0x00) {
1020 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1023 /* Loop through all args */
1025 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1027 if (!create_full_path(thisArg)) {
1028 WCMD_print_error ();
1034 /* Parse the /A options given by the user on the commandline
1035 * into a bitmask of wanted attributes (*wantSet),
1036 * and a bitmask of unwanted attributes (*wantClear).
1038 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1039 static const WCHAR parmA[] = {'/','A','\0'};
1042 /* both are strictly 'out' parameters */
1046 /* For each /A argument */
1047 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1048 /* Skip /A itself */
1051 /* Skip optional : */
1054 /* For each of the attribute specifier chars to this /A option */
1055 for (; *p != 0 && *p != '/'; p++) {
1056 BOOL negate = FALSE;
1064 /* Convert the attribute specifier to a bit in one of the masks */
1066 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1067 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1068 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1069 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1071 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1081 /* If filename part of parameter is * or *.*,
1082 * and neither /Q nor /P options were given,
1083 * prompt the user whether to proceed.
1084 * Returns FALSE if user says no, TRUE otherwise.
1085 * *pPrompted is set to TRUE if the user is prompted.
1086 * (If /P supplied, del will prompt for individual files later.)
1088 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1089 static const WCHAR parmP[] = {'/','P','\0'};
1090 static const WCHAR parmQ[] = {'/','Q','\0'};
1092 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1093 static const WCHAR anyExt[]= {'.','*','\0'};
1095 WCHAR dir[MAX_PATH];
1096 WCHAR fname[MAX_PATH];
1097 WCHAR ext[MAX_PATH];
1098 WCHAR fpath[MAX_PATH];
1100 /* Convert path into actual directory spec */
1101 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1102 WCMD_splitpath(fpath, drive, dir, fname, ext);
1104 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1105 if ((strcmpW(fname, starW) == 0) &&
1106 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1108 WCHAR question[MAXSTRING];
1109 static const WCHAR fmt[] = {'%','s',' ','\0'};
1111 /* Caller uses this to suppress "file not found" warning later */
1114 /* Ask for confirmation */
1115 wsprintfW(question, fmt, fpath);
1116 return WCMD_ask_confirm(question, TRUE, NULL);
1119 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1123 /* Helper function for WCMD_delete().
1124 * Deletes a single file, directory, or wildcard.
1125 * If /S was given, does it recursively.
1126 * Returns TRUE if a file was deleted.
1128 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1130 static const WCHAR parmP[] = {'/','P','\0'};
1131 static const WCHAR parmS[] = {'/','S','\0'};
1132 static const WCHAR parmF[] = {'/','F','\0'};
1134 DWORD unwanted_attrs;
1136 WCHAR argCopy[MAX_PATH];
1137 WIN32_FIND_DATAW fd;
1139 WCHAR fpath[MAX_PATH];
1141 BOOL handleParm = TRUE;
1143 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1145 strcpyW(argCopy, thisArg);
1146 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1147 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1149 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1150 /* Skip this arg if user declines to delete *.* */
1154 /* First, try to delete in the current directory */
1155 hff = FindFirstFileW(argCopy, &fd);
1156 if (hff == INVALID_HANDLE_VALUE) {
1162 /* Support del <dirname> by just deleting all files dirname\* */
1164 && (strchrW(argCopy,'*') == NULL)
1165 && (strchrW(argCopy,'?') == NULL)
1166 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1168 WCHAR modifiedParm[MAX_PATH];
1169 static const WCHAR slashStar[] = {'\\','*','\0'};
1171 strcpyW(modifiedParm, argCopy);
1172 strcatW(modifiedParm, slashStar);
1175 WCMD_delete_one(modifiedParm);
1177 } else if (handleParm) {
1179 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1180 strcpyW (fpath, argCopy);
1182 p = strrchrW (fpath, '\\');
1185 strcatW (fpath, fd.cFileName);
1187 else strcpyW (fpath, fd.cFileName);
1188 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1191 /* Handle attribute matching (/A) */
1192 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1193 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1195 /* /P means prompt for each file */
1196 if (ok && strstrW (quals, parmP) != NULL) {
1199 /* Ask for confirmation */
1200 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1201 ok = WCMD_ask_confirm(question, FALSE, NULL);
1202 LocalFree(question);
1205 /* Only proceed if ok to */
1208 /* If file is read only, and /A:r or /F supplied, delete it */
1209 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1210 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1211 strstrW (quals, parmF) != NULL)) {
1212 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1215 /* Now do the delete */
1216 if (!DeleteFileW(fpath)) WCMD_print_error ();
1220 } while (FindNextFileW(hff, &fd) != 0);
1224 /* Now recurse into all subdirectories handling the parameter in the same way */
1225 if (strstrW (quals, parmS) != NULL) {
1227 WCHAR thisDir[MAX_PATH];
1231 WCHAR dir[MAX_PATH];
1232 WCHAR fname[MAX_PATH];
1233 WCHAR ext[MAX_PATH];
1235 /* Convert path into actual directory spec */
1236 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1237 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1239 strcpyW(thisDir, drive);
1240 strcatW(thisDir, dir);
1241 cPos = strlenW(thisDir);
1243 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1245 /* Append '*' to the directory */
1246 thisDir[cPos] = '*';
1247 thisDir[cPos+1] = 0x00;
1249 hff = FindFirstFileW(thisDir, &fd);
1251 /* Remove residual '*' */
1252 thisDir[cPos] = 0x00;
1254 if (hff != INVALID_HANDLE_VALUE) {
1255 DIRECTORY_STACK *allDirs = NULL;
1256 DIRECTORY_STACK *lastEntry = NULL;
1259 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1260 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1261 (strcmpW(fd.cFileName, dotW) != 0)) {
1263 DIRECTORY_STACK *nextDir;
1264 WCHAR subParm[MAX_PATH];
1266 /* Work out search parameter in sub dir */
1267 strcpyW (subParm, thisDir);
1268 strcatW (subParm, fd.cFileName);
1269 strcatW (subParm, slashW);
1270 strcatW (subParm, fname);
1271 strcatW (subParm, ext);
1272 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1274 /* Allocate memory, add to list */
1275 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
1276 if (allDirs == NULL) allDirs = nextDir;
1277 if (lastEntry != NULL) lastEntry->next = nextDir;
1278 lastEntry = nextDir;
1279 nextDir->next = NULL;
1280 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
1281 (strlenW(subParm)+1) * sizeof(WCHAR));
1282 strcpyW(nextDir->dirName, subParm);
1284 } while (FindNextFileW(hff, &fd) != 0);
1287 /* Go through each subdir doing the delete */
1288 while (allDirs != NULL) {
1289 DIRECTORY_STACK *tempDir;
1291 tempDir = allDirs->next;
1292 found |= WCMD_delete_one (allDirs->dirName);
1294 HeapFree(GetProcessHeap(),0,allDirs->dirName);
1295 HeapFree(GetProcessHeap(),0,allDirs);
1304 /****************************************************************************
1307 * Delete a file or wildcarded set.
1310 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1311 * - Each set is a pattern, eg /ahr /as-r means
1312 * readonly+hidden OR nonreadonly system files
1313 * - The '-' applies to a single field, ie /a:-hr means read only
1317 BOOL WCMD_delete (WCHAR *args) {
1320 BOOL argsProcessed = FALSE;
1321 BOOL foundAny = FALSE;
1325 for (argno=0; ; argno++) {
1330 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1332 break; /* no more parameters */
1334 continue; /* skip options */
1336 argsProcessed = TRUE;
1337 found = WCMD_delete_one(thisArg);
1340 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1345 /* Handle no valid args */
1347 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1355 * Returns a trimmed version of s with all leading and trailing whitespace removed
1359 static WCHAR *WCMD_strtrim(const WCHAR *s)
1361 DWORD len = strlenW(s);
1362 const WCHAR *start = s;
1365 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
1368 while (isspaceW(*start)) start++;
1370 const WCHAR *end = s + len - 1;
1371 while (end > start && isspaceW(*end)) end--;
1372 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1373 result[end - start + 1] = '\0';
1381 /****************************************************************************
1384 * Echo input to the screen (or not). We don't try to emulate the bugs
1385 * in DOS (try typing "ECHO ON AGAIN" for an example).
1388 void WCMD_echo (const WCHAR *args)
1391 const WCHAR *origcommand = args;
1394 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1395 || args[0]==':' || args[0]==';')
1398 trimmed = WCMD_strtrim(args);
1399 if (!trimmed) return;
1401 count = strlenW(trimmed);
1402 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1403 && origcommand[0]!=';') {
1404 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1405 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1406 HeapFree(GetProcessHeap(), 0, trimmed);
1410 if (lstrcmpiW(trimmed, onW) == 0)
1412 else if (lstrcmpiW(trimmed, offW) == 0)
1415 WCMD_output_asis (args);
1416 WCMD_output_asis (newlineW);
1418 HeapFree(GetProcessHeap(), 0, trimmed);
1421 /*****************************************************************************
1424 * Execute a command, and any && or bracketed follow on to the command. The
1425 * first command to be executed may not be at the front of the
1426 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1428 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1429 BOOL isIF, BOOL executecmds)
1431 CMD_LIST *curPosition = *cmdList;
1432 int myDepth = (*cmdList)->bracketDepth;
1434 WINE_TRACE("cmdList(%p), firstCmd(%p), doIt(%d)\n",
1435 cmdList, wine_dbgstr_w(firstcmd),
1438 /* Skip leading whitespace between condition and the command */
1439 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1441 /* Process the first command, if there is one */
1442 if (executecmds && firstcmd && *firstcmd) {
1443 WCHAR *command = WCMD_strdupW(firstcmd);
1444 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1445 HeapFree(GetProcessHeap(), 0, command);
1449 /* If it didn't move the position, step to next command */
1450 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1452 /* Process any other parts of the command */
1454 BOOL processThese = executecmds;
1457 static const WCHAR ifElse[] = {'e','l','s','e'};
1459 /* execute all appropriate commands */
1460 curPosition = *cmdList;
1462 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1464 (*cmdList)->prevDelim,
1465 (*cmdList)->bracketDepth, myDepth);
1467 /* Execute any statements appended to the line */
1468 /* FIXME: Only if previous call worked for && or failed for || */
1469 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1470 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1471 if (processThese && (*cmdList)->command) {
1472 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1475 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1477 /* Execute any appended to the statement with (...) */
1478 } else if ((*cmdList)->bracketDepth > myDepth) {
1480 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1481 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1483 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1485 /* End of the command - does 'ELSE ' follow as the next command? */
1488 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1489 (*cmdList)->command)) {
1491 /* Swap between if and else processing */
1492 processThese = !processThese;
1494 /* Process the ELSE part */
1496 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1497 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1499 /* Skip leading whitespace between condition and the command */
1500 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1502 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1505 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1507 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1516 /*****************************************************************************
1517 * WCMD_parse_forf_options
1519 * Parses the for /f 'options', extracting the values and validating the
1520 * keywords. Note all keywords are optional.
1522 * options [I] The unparsed parameter string
1523 * eol [O] Set to the comment character (eol=x)
1524 * skip [O] Set to the number of lines to skip (skip=xx)
1525 * delims [O] Set to the token delimiters (delims=)
1526 * tokens [O] Set to the requested tokens, as provided (tokens=)
1527 * usebackq [O] Set to TRUE if usebackq found
1529 * Returns TRUE on success, FALSE on syntax error
1532 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1533 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1536 WCHAR *pos = options;
1537 int len = strlenW(pos);
1538 static const WCHAR eolW[] = {'e','o','l','='};
1539 static const WCHAR skipW[] = {'s','k','i','p','='};
1540 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1541 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1542 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1543 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1544 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1546 /* Initialize to defaults */
1547 strcpyW(delims, forf_defaultdelims);
1548 strcpyW(tokens, forf_defaulttokens);
1553 /* Strip (optional) leading and trailing quotes */
1554 if ((*pos == '"') && (pos[len-1] == '"')) {
1559 /* Process each keyword */
1560 while (pos && *pos) {
1561 if (*pos == ' ' || *pos == '\t') {
1564 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1565 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1566 pos, sizeof(eolW)/sizeof(WCHAR),
1567 eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1568 *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1569 pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1570 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1572 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1573 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1574 pos, sizeof(skipW)/sizeof(WCHAR),
1575 skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1576 WCHAR *nextchar = NULL;
1577 pos = pos + sizeof(skipW)/sizeof(WCHAR);
1578 *skip = strtoulW(pos, &nextchar, 0);
1579 WINE_TRACE("Found skip as %d lines\n", *skip);
1582 /* Save if usebackq semantics are in effect */
1583 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1584 pos, sizeof(usebackqW)/sizeof(WCHAR),
1585 usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1587 pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1588 WINE_TRACE("Found usebackq\n");
1590 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1591 if you finish the optionsroot string with delims= otherwise the space is
1592 just a token delimiter! */
1593 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1594 pos, sizeof(delimsW)/sizeof(WCHAR),
1595 delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1598 pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1599 while (*pos && *pos != ' ') {
1603 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1604 delims[i++] = 0; /* Null terminate the delims */
1605 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1607 /* Save the tokens being requested */
1608 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1609 pos, sizeof(tokensW)/sizeof(WCHAR),
1610 tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1613 pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1614 while (*pos && *pos != ' ') {
1618 tokens[i++] = 0; /* Null terminate the tokens */
1619 WINE_FIXME("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1622 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1629 /*****************************************************************************
1630 * WCMD_add_dirstowalk
1632 * When recursing through directories (for /r), we need to add to the list of
1633 * directories still to walk, any subdirectories of the one we are processing.
1636 * options [I] The remaining list of directories still to process
1638 * Note this routine inserts the subdirectories found between the entry being
1639 * processed, and any other directory still to be processed, mimicing what
1642 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1643 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1644 WCHAR fullitem[MAX_PATH];
1645 WIN32_FIND_DATAW fd;
1648 /* Build a generic search and add all directories on the list of directories
1650 strcpyW(fullitem, dirsToWalk->dirName);
1651 strcatW(fullitem, slashstarW);
1652 hff = FindFirstFileW(fullitem, &fd);
1653 if (hff != INVALID_HANDLE_VALUE) {
1655 WINE_TRACE("Looking for subdirectories\n");
1656 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1657 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1658 (strcmpW(fd.cFileName, dotW) != 0))
1660 /* Allocate memory, add to list */
1661 DIRECTORY_STACK *toWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1662 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1663 toWalk->next = remainingDirs->next;
1664 remainingDirs->next = toWalk;
1665 remainingDirs = toWalk;
1666 toWalk->dirName = HeapAlloc(GetProcessHeap(), 0,
1668 (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1669 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1670 strcatW(toWalk->dirName, slashW);
1671 strcatW(toWalk->dirName, fd.cFileName);
1672 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1673 toWalk, toWalk->next);
1675 } while (FindNextFileW(hff, &fd) != 0);
1676 WINE_TRACE("Finished adding all subdirectories\n");
1681 /**************************************************************************
1684 * When parsing file or string contents (for /f), once the string to parse
1685 * has been identified, handle the various options and call the do part
1689 * cmdStart [I] - Identifies the list of commands making up the
1690 * for loop body (especially if brackets in use)
1691 * firstCmd [I] - The textual start of the command after the DO
1692 * which is within the first item of cmdStart
1693 * cmdEnd [O] - Identifies where to continue after the DO
1694 * variable [I] - The variable identified on the for line
1695 * buffer [I] - The string to parse
1696 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1697 * forf_skip [I/O] - How many lines to skip first
1698 * forf_eol [I] - The 'end of line' (comment) character
1699 * forf_delims [I] - The delimiters to use when breaking the string apart
1701 static void WCMD_parse_line(CMD_LIST *cmdStart,
1702 const WCHAR *firstCmd,
1704 const WCHAR variable,
1709 WCHAR *forf_delims) {
1711 WCHAR *parm, *where;
1712 FOR_CONTEXT oldcontext;
1715 /* Skip lines if requested */
1721 /* Save away any existing for variable context (e.g. nested for loops) */
1722 oldcontext = forloopcontext;
1724 /* Extract the parameter */
1725 parm = WCMD_parameter_with_delims(buffer, 0, &where, FALSE, FALSE, forf_delims);
1726 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1727 wine_dbgstr_w(buffer));
1729 /* FIXME: Use tokens= line to populate forloopcontext */
1730 varidx = FOR_VAR_IDX(variable);
1731 if (varidx >=0) forloopcontext.variable[varidx] = WCMD_strdupW(parm);
1733 if (where && where[0] != forf_eol) {
1734 CMD_LIST *thisCmdStart = cmdStart;
1736 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
1737 *cmdEnd = thisCmdStart;
1740 if (varidx >=0) HeapFree(GetProcessHeap(), 0, forloopcontext.variable[varidx]);
1742 /* Restore the original for variable contextx */
1743 forloopcontext = oldcontext;
1746 /**************************************************************************
1747 * WCMD_forf_getinputhandle
1749 * Return a file handle which can be used for reading the input lines,
1750 * either to a specific file (which may be quote delimited as we have to
1751 * read the parameters in raw mode) or to a command which we need to
1752 * execute. The command being executed runs in its own shell and stores
1753 * its data in a temporary file.
1756 * usebackq [I] - Indicates whether usebackq is in effect or not
1757 * itemStr [I] - The item to be handled, either a filename or
1758 * whole command string to execute
1759 * iscmd [I] - Identifies whether this is a command or not
1761 * Returns a file handle which can be used to read the input lines from.
1763 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
1764 WCHAR temp_str[MAX_PATH];
1765 WCHAR temp_file[MAX_PATH];
1766 WCHAR temp_cmd[MAXSTRING];
1767 HANDLE hinput = INVALID_HANDLE_VALUE;
1768 static const WCHAR redirOutW[] = {'>','%','s','\0'};
1769 static const WCHAR cmdW[] = {'C','M','D','\0'};
1770 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
1771 '/','C',' ','"','%','s','"','\0'};
1773 /* Remove leading and trailing character */
1774 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
1775 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
1776 (!iscmd && (itemstr[0] == '"' && usebackq)))
1778 itemstr[strlenW(itemstr)-1] = 0x00;
1783 /* Get temp filename */
1784 GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
1785 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
1787 /* Redirect output to the temporary file */
1788 wsprintfW(temp_str, redirOutW, temp_file);
1789 wsprintfW(temp_cmd, cmdslashcW, itemstr);
1790 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
1791 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
1792 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
1794 /* Open the file, read line by line and process */
1795 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1796 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
1799 /* Open the file, read line by line and process */
1800 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
1801 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
1802 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1807 /**************************************************************************
1810 * Batch file loop processing.
1812 * On entry: cmdList contains the syntax up to the set
1813 * next cmdList and all in that bracket contain the set data
1814 * next cmdlist contains the DO cmd
1815 * following that is either brackets or && entries (as per if)
1819 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1821 WIN32_FIND_DATAW fd;
1824 static const WCHAR inW[] = {'i','n'};
1825 static const WCHAR doW[] = {'d','o'};
1826 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1829 WCHAR *oldvariablevalue;
1832 WCHAR optionsRoot[MAX_PATH];
1833 DIRECTORY_STACK *dirsToWalk = NULL;
1834 BOOL expandDirs = FALSE;
1835 BOOL useNumbers = FALSE;
1836 BOOL doFileset = FALSE;
1837 BOOL doRecurse = FALSE;
1838 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
1839 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1841 CMD_LIST *thisCmdStart;
1842 int parameterNo = 0;
1845 WCHAR forf_delims[256];
1846 WCHAR forf_tokens[MAXSTRING];
1847 BOOL forf_usebackq = FALSE;
1849 /* Handle optional qualifiers (multiple are allowed) */
1850 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
1853 while (thisArg && *thisArg == '/') {
1854 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
1856 switch (toupperW(*thisArg)) {
1857 case 'D': expandDirs = TRUE; break;
1858 case 'L': useNumbers = TRUE; break;
1860 /* Recursive is special case - /R can have an optional path following it */
1861 /* filenamesets are another special case - /F can have an optional options following it */
1865 /* When recursing directories, use current directory as the starting point unless
1866 subsequently overridden */
1867 doRecurse = (toupperW(*thisArg) == 'R');
1868 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
1870 doFileset = (toupperW(*thisArg) == 'F');
1872 /* Retrieve next parameter to see if is root/options (raw form required
1873 with for /f, or unquoted in for /r) */
1874 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
1876 /* Next parm is either qualifier, path/options or variable -
1877 only care about it if it is the path/options */
1878 if (thisArg && *thisArg != '/' && *thisArg != '%') {
1880 strcpyW(optionsRoot, thisArg);
1885 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
1888 /* Step to next token */
1889 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
1892 /* Ensure line continues with variable */
1893 if (!*thisArg || *thisArg != '%') {
1894 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1898 /* With for /f parse the options if provided */
1900 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
1901 forf_delims, forf_tokens, &forf_usebackq))
1903 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1907 /* Set up the list of directories to recurse if we are going to */
1908 } else if (doRecurse) {
1909 /* Allocate memory, add to list */
1910 dirsToWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1911 dirsToWalk->next = NULL;
1912 dirsToWalk->dirName = HeapAlloc(GetProcessHeap(),0,
1913 (strlenW(optionsRoot) + 1) * sizeof(WCHAR));
1914 strcpyW(dirsToWalk->dirName, optionsRoot);
1915 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
1918 /* Variable should follow */
1919 strcpyW(variable, thisArg);
1920 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1921 varidx = FOR_VAR_IDX(variable[1]);
1923 /* Ensure line continues with IN */
1924 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
1926 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1927 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
1928 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
1929 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1933 /* Save away where the set of data starts and the variable */
1934 thisDepth = (*cmdList)->bracketDepth;
1935 *cmdList = (*cmdList)->nextcommand;
1936 setStart = (*cmdList);
1938 /* Skip until the close bracket */
1939 WINE_TRACE("Searching %p as the set\n", *cmdList);
1941 (*cmdList)->command != NULL &&
1942 (*cmdList)->bracketDepth > thisDepth) {
1943 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1944 *cmdList = (*cmdList)->nextcommand;
1947 /* Skip the close bracket, if there is one */
1948 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1950 /* Syntax error if missing close bracket, or nothing following it
1951 and once we have the complete set, we expect a DO */
1952 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1953 if ((*cmdList == NULL)
1954 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1956 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1962 /* Loop repeatedly per-directory we are potentially walking, when in for /r
1963 mode, or once for the rest of the time. */
1966 /* Save away the starting position for the commands (and offset for the
1968 cmdStart = *cmdList;
1969 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1972 /* If we are recursing directories (ie /R), add all sub directories now, then
1973 prefix the root when searching for the item */
1974 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
1977 /* Loop through all set entries */
1979 thisSet->command != NULL &&
1980 thisSet->bracketDepth >= thisDepth) {
1982 /* Loop through all entries on the same line */
1985 WCHAR buffer[MAXSTRING];
1987 WINE_TRACE("Processing for set %p\n", thisSet);
1989 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
1992 * If the parameter within the set has a wildcard then search for matching files
1993 * otherwise do a literal substitution.
1995 static const WCHAR wildcards[] = {'*','?','\0'};
1996 thisCmdStart = cmdStart;
1999 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2001 if (!useNumbers && !doFileset) {
2002 WCHAR fullitem[MAX_PATH];
2004 /* Now build the item to use / search for in the specified directory,
2005 as it is fully qualified in the /R case */
2007 strcpyW(fullitem, dirsToWalk->dirName);
2008 strcatW(fullitem, slashW);
2009 strcatW(fullitem, item);
2011 strcpyW(fullitem, item);
2014 if (strpbrkW (fullitem, wildcards)) {
2016 hff = FindFirstFileW(fullitem, &fd);
2017 if (hff != INVALID_HANDLE_VALUE) {
2019 BOOL isDirectory = FALSE;
2021 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2023 /* Handle as files or dirs appropriately, but ignore . and .. */
2024 if (isDirectory == expandDirs &&
2025 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2026 (strcmpW(fd.cFileName, dotW) != 0))
2028 thisCmdStart = cmdStart;
2029 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2032 strcpyW(fullitem, dirsToWalk->dirName);
2033 strcatW(fullitem, slashW);
2034 strcatW(fullitem, fd.cFileName);
2036 strcpyW(fullitem, fd.cFileName);
2040 /* Save away any existing for variable context (e.g. nested for loops)
2041 and restore it after executing the body of this for loop */
2043 oldvariablevalue = forloopcontext.variable[varidx];
2044 forloopcontext.variable[varidx] = fullitem;
2046 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2047 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2049 cmdEnd = thisCmdStart;
2051 } while (FindNextFileW(hff, &fd) != 0);
2057 /* Save away any existing for variable context (e.g. nested for loops)
2058 and restore it after executing the body of this for loop */
2060 oldvariablevalue = forloopcontext.variable[varidx];
2061 forloopcontext.variable[varidx] = fullitem;
2063 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2064 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2066 cmdEnd = thisCmdStart;
2069 } else if (useNumbers) {
2070 /* Convert the first 3 numbers to signed longs and save */
2071 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2072 /* else ignore them! */
2074 /* Filesets - either a list of files, or a command to run and parse the output */
2075 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2076 (forf_usebackq && *itemStart != '\''))) {
2081 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2082 wine_dbgstr_w(item));
2084 /* If backquote or single quote, we need to launch that command
2085 and parse the results - use a temporary file */
2086 if ((forf_usebackq && *itemStart == '`') ||
2087 (!forf_usebackq && *itemStart == '\'')) {
2089 /* Use itemstart because the command is the whole set, not just the first token */
2090 itemparm = itemStart;
2093 /* Use item because the file to process is just the first item in the set */
2096 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2098 /* Process the input file */
2099 if (input == INVALID_HANDLE_VALUE) {
2100 WCMD_print_error ();
2101 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2103 return; /* FOR loop aborts at first failure here */
2107 /* Read line by line until end of file */
2108 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2109 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2110 &forf_skip, forf_eol, forf_delims);
2113 CloseHandle (input);
2116 /* When we have processed the item as a whole command, abort future set processing */
2117 if (itemparm==itemStart) {
2122 /* Filesets - A string literal */
2123 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2124 (forf_usebackq && *itemStart == '\''))) {
2126 /* Remove leading and trailing character, ready to parse with delims= delimiters
2127 Note that the last quote is removed from the set and the string terminates
2128 there to mimic windows */
2129 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2135 /* Copy the item away from the global buffer used by WCMD_parameter */
2136 strcpyW(buffer, itemStart);
2137 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2138 &forf_skip, forf_eol, forf_delims);
2140 /* Only one string can be supplied in the whole set, abort future set processing */
2145 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2149 /* Move onto the next set line */
2150 if (thisSet) thisSet = thisSet->nextcommand;
2153 /* If /L is provided, now run the for loop */
2156 static const WCHAR fmt[] = {'%','d','\0'};
2158 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2159 numbers[0], numbers[2], numbers[1]);
2161 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2164 sprintfW(thisNum, fmt, i);
2165 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2167 thisCmdStart = cmdStart;
2170 /* Save away any existing for variable context (e.g. nested for loops)
2171 and restore it after executing the body of this for loop */
2173 oldvariablevalue = forloopcontext.variable[varidx];
2174 forloopcontext.variable[varidx] = thisNum;
2176 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2177 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2179 cmdEnd = thisCmdStart;
2182 /* If we are walking directories, move on to any which remain */
2183 if (dirsToWalk != NULL) {
2184 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2185 HeapFree(GetProcessHeap(), 0, dirsToWalk->dirName);
2186 HeapFree(GetProcessHeap(), 0, dirsToWalk);
2187 dirsToWalk = nextDir;
2188 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2189 wine_dbgstr_w(dirsToWalk->dirName));
2190 else WINE_TRACE("Finished all directories.\n");
2193 } while (dirsToWalk != NULL);
2195 /* Now skip over the do part if we did not perform the for loop so far.
2196 We store in cmdEnd the next command after the do block, but we only
2197 know this if something was run. If it has not been, we need to calculate
2200 thisCmdStart = cmdStart;
2201 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2202 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2203 cmdEnd = thisCmdStart;
2206 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2207 all processing, OR it should be pointing to the end of && processing OR
2208 it should be pointing at the NULL end of bracket for the DO. The return
2209 value needs to be the NEXT command to execute, which it either is, or
2210 we need to step over the closing bracket */
2212 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2215 /**************************************************************************
2218 * Simple on-line help. Help text is stored in the resource file.
2221 void WCMD_give_help (const WCHAR *args)
2225 args = WCMD_skip_leading_spaces((WCHAR*) args);
2226 if (strlenW(args) == 0) {
2227 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2230 /* Display help message for builtin commands */
2231 for (i=0; i<=WCMD_EXIT; i++) {
2232 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2233 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2234 WCMD_output_asis (WCMD_LoadMessage(i));
2238 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2239 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2240 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2241 args, -1, externals[i], -1) == CSTR_EQUAL) {
2243 static const WCHAR helpW[] = {' ', '/','?','\0'};
2245 strcatW(cmd, helpW);
2246 WCMD_run_program(cmd, FALSE);
2250 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2255 /****************************************************************************
2258 * Batch file jump instruction. Not the most efficient algorithm ;-)
2259 * Prints error message if the specified label cannot be found - the file pointer is
2260 * then at EOF, effectively stopping the batch file.
2261 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2264 void WCMD_goto (CMD_LIST **cmdList) {
2266 WCHAR string[MAX_PATH];
2267 WCHAR current[MAX_PATH];
2269 /* Do not process any more parts of a processed multipart or multilines command */
2270 if (cmdList) *cmdList = NULL;
2272 if (context != NULL) {
2273 WCHAR *paramStart = param1, *str;
2274 static const WCHAR eofW[] = {':','e','o','f','\0'};
2276 if (param1[0] == 0x00) {
2277 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2281 /* Handle special :EOF label */
2282 if (lstrcmpiW (eofW, param1) == 0) {
2283 context -> skip_rest = TRUE;
2287 /* Support goto :label as well as goto label */
2288 if (*paramStart == ':') paramStart++;
2290 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2291 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2293 while (isspaceW (*str)) str++;
2297 while (((current[index] = str[index])) && (!isspaceW (current[index])))
2300 /* ignore space at the end */
2302 if (lstrcmpiW (current, paramStart) == 0) return;
2305 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2310 /*****************************************************************************
2313 * Push a directory onto the stack
2316 void WCMD_pushd (const WCHAR *args)
2318 struct env_stack *curdir;
2320 static const WCHAR parmD[] = {'/','D','\0'};
2322 if (strchrW(args, '/') != NULL) {
2323 SetLastError(ERROR_INVALID_PARAMETER);
2328 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2329 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2330 if( !curdir || !thisdir ) {
2333 WINE_ERR ("out of memory\n");
2337 /* Change directory using CD code with /D parameter */
2338 strcpyW(quals, parmD);
2339 GetCurrentDirectoryW (1024, thisdir);
2341 WCMD_setshow_default(args);
2347 curdir -> next = pushd_directories;
2348 curdir -> strings = thisdir;
2349 if (pushd_directories == NULL) {
2350 curdir -> u.stackdepth = 1;
2352 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2354 pushd_directories = curdir;
2359 /*****************************************************************************
2362 * Pop a directory from the stack
2365 void WCMD_popd (void) {
2366 struct env_stack *temp = pushd_directories;
2368 if (!pushd_directories)
2371 /* pop the old environment from the stack, and make it the current dir */
2372 pushd_directories = temp->next;
2373 SetCurrentDirectoryW(temp->strings);
2374 LocalFree (temp->strings);
2378 /*******************************************************************
2379 * evaluate_if_comparison
2381 * Evaluates an "if" comparison operation
2384 * leftOperand [I] left operand, non NULL
2385 * operator [I] "if" binary comparison operator, non NULL
2386 * rightOperand [I] right operand, non NULL
2387 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2390 * Success: 1 if operator applied to the operands evaluates to TRUE
2391 * 0 if operator applied to the operands evaluates to FALSE
2392 * Failure: -1 if operator is not recognized
2394 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2395 const WCHAR *rightOperand, int caseInsensitive)
2397 WCHAR *endptr_leftOp, *endptr_rightOp;
2398 long int leftOperand_int, rightOperand_int;
2400 static const WCHAR lssW[] = {'l','s','s','\0'};
2401 static const WCHAR leqW[] = {'l','e','q','\0'};
2402 static const WCHAR equW[] = {'e','q','u','\0'};
2403 static const WCHAR neqW[] = {'n','e','q','\0'};
2404 static const WCHAR geqW[] = {'g','e','q','\0'};
2405 static const WCHAR gtrW[] = {'g','t','r','\0'};
2407 /* == is a special case, as it always compares strings */
2408 if (!lstrcmpiW(operator, eqeqW))
2409 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2410 : lstrcmpW (leftOperand, rightOperand) == 0;
2412 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2413 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2414 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2415 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2417 /* Perform actual (integer or string) comparison */
2418 if (!lstrcmpiW(operator, lssW)) {
2420 return leftOperand_int < rightOperand_int;
2422 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2423 : lstrcmpW (leftOperand, rightOperand) < 0;
2426 if (!lstrcmpiW(operator, leqW)) {
2428 return leftOperand_int <= rightOperand_int;
2430 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2431 : lstrcmpW (leftOperand, rightOperand) <= 0;
2434 if (!lstrcmpiW(operator, equW)) {
2436 return leftOperand_int == rightOperand_int;
2438 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2439 : lstrcmpW (leftOperand, rightOperand) == 0;
2442 if (!lstrcmpiW(operator, neqW)) {
2444 return leftOperand_int != rightOperand_int;
2446 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2447 : lstrcmpW (leftOperand, rightOperand) != 0;
2450 if (!lstrcmpiW(operator, geqW)) {
2452 return leftOperand_int >= rightOperand_int;
2454 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2455 : lstrcmpW (leftOperand, rightOperand) >= 0;
2458 if (!lstrcmpiW(operator, gtrW)) {
2460 return leftOperand_int > rightOperand_int;
2462 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2463 : lstrcmpW (leftOperand, rightOperand) > 0;
2469 /****************************************************************************
2472 * Batch file conditional.
2474 * On entry, cmdlist will point to command containing the IF, and optionally
2475 * the first command to execute (if brackets not found)
2476 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2477 * If ('s were found, execute all within that bracket
2478 * Command may optionally be followed by an ELSE - need to skip instructions
2479 * in the else using the same logic
2481 * FIXME: Much more syntax checking needed!
2483 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2485 int negate; /* Negate condition */
2486 int test; /* Condition evaluation result */
2487 WCHAR condition[MAX_PATH], *command;
2488 static const WCHAR notW[] = {'n','o','t','\0'};
2489 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2490 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2491 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2492 static const WCHAR parmI[] = {'/','I','\0'};
2493 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2495 negate = !lstrcmpiW(param1,notW);
2496 strcpyW(condition, (negate ? param2 : param1));
2497 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2499 if (!lstrcmpiW (condition, errlvlW)) {
2500 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2502 long int param_int = strtolW(param, &endptr, 10);
2503 if (*endptr) goto syntax_err;
2504 test = ((long int)errorlevel >= param_int);
2505 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2507 else if (!lstrcmpiW (condition, existW)) {
2508 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2509 != INVALID_FILE_ATTRIBUTES);
2510 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2512 else if (!lstrcmpiW (condition, defdW)) {
2513 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2515 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2517 else { /* comparison operation */
2518 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2521 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, ¶mStart, TRUE, FALSE));
2525 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2526 p = paramStart + strlenW(leftOperand);
2527 while (*p == ' ' || *p == '\t')
2530 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2531 strcpyW(operator, eqeqW);
2533 strcpyW(operator, WCMD_parameter(p, 0, ¶mStart, FALSE, FALSE));
2534 if (!*operator) goto syntax_err;
2536 p += strlenW(operator);
2538 strcpyW(rightOperand, WCMD_parameter(p, 0, ¶mStart, TRUE, FALSE));
2542 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2546 p = paramStart + strlenW(rightOperand);
2547 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2550 /* Process rest of IF statement which is on the same line
2551 Note: This may process all or some of the cmdList (eg a GOTO) */
2552 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2556 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2559 /****************************************************************************
2562 * Move a file, directory tree or wildcarded set of files.
2565 void WCMD_move (void)
2568 WIN32_FIND_DATAW fd;
2570 WCHAR input[MAX_PATH];
2571 WCHAR output[MAX_PATH];
2573 WCHAR dir[MAX_PATH];
2574 WCHAR fname[MAX_PATH];
2575 WCHAR ext[MAX_PATH];
2577 if (param1[0] == 0x00) {
2578 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2582 /* If no destination supplied, assume current directory */
2583 if (param2[0] == 0x00) {
2584 strcpyW(param2, dotW);
2587 /* If 2nd parm is directory, then use original filename */
2588 /* Convert partial path to full path */
2589 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2590 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2591 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2592 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2594 /* Split into components */
2595 WCMD_splitpath(input, drive, dir, fname, ext);
2597 hff = FindFirstFileW(input, &fd);
2598 if (hff == INVALID_HANDLE_VALUE)
2602 WCHAR dest[MAX_PATH];
2603 WCHAR src[MAX_PATH];
2607 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2609 /* Build src & dest name */
2610 strcpyW(src, drive);
2613 /* See if dest is an existing directory */
2614 attribs = GetFileAttributesW(output);
2615 if (attribs != INVALID_FILE_ATTRIBUTES &&
2616 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2617 strcpyW(dest, output);
2618 strcatW(dest, slashW);
2619 strcatW(dest, fd.cFileName);
2621 strcpyW(dest, output);
2624 strcatW(src, fd.cFileName);
2626 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2627 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2629 /* If destination exists, prompt unless /Y supplied */
2630 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2632 WCHAR copycmd[MAXSTRING];
2635 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2636 if (strstrW (quals, parmNoY))
2638 else if (strstrW (quals, parmY))
2641 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2642 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2643 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2644 && ! lstrcmpiW (copycmd, parmY));
2647 /* Prompt if overwriting */
2651 /* Ask for confirmation */
2652 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2653 ok = WCMD_ask_confirm(question, FALSE, NULL);
2654 LocalFree(question);
2656 /* So delete the destination prior to the move */
2658 if (!DeleteFileW(dest)) {
2659 WCMD_print_error ();
2668 status = MoveFileW(src, dest);
2670 status = 1; /* Anything other than 0 to prevent error msg below */
2674 WCMD_print_error ();
2677 } while (FindNextFileW(hff, &fd) != 0);
2682 /****************************************************************************
2685 * Suspend execution of a batch script until a key is typed
2688 void WCMD_pause (void)
2694 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2696 have_console = GetConsoleMode(hIn, &oldmode);
2698 SetConsoleMode(hIn, 0);
2700 WCMD_output_asis(anykey);
2701 WCMD_ReadFile(hIn, &key, 1, &count);
2703 SetConsoleMode(hIn, oldmode);
2706 /****************************************************************************
2709 * Delete a directory.
2712 void WCMD_remove_dir (WCHAR *args) {
2715 int argsProcessed = 0;
2717 static const WCHAR parmS[] = {'/','S','\0'};
2718 static const WCHAR parmQ[] = {'/','Q','\0'};
2720 /* Loop through all args */
2722 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2723 if (argN && argN[0] != '/') {
2724 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2725 wine_dbgstr_w(quals));
2728 /* If subdirectory search not supplied, just try to remove
2729 and report error if it fails (eg if it contains a file) */
2730 if (strstrW (quals, parmS) == NULL) {
2731 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2733 /* Otherwise use ShFileOp to recursively remove a directory */
2736 SHFILEOPSTRUCTW lpDir;
2739 if (strstrW (quals, parmQ) == NULL) {
2741 WCHAR question[MAXSTRING];
2742 static const WCHAR fmt[] = {'%','s',' ','\0'};
2744 /* Ask for confirmation */
2745 wsprintfW(question, fmt, thisArg);
2746 ok = WCMD_ask_confirm(question, TRUE, NULL);
2748 /* Abort if answer is 'N' */
2755 lpDir.pFrom = thisArg;
2756 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2757 lpDir.wFunc = FO_DELETE;
2759 /* SHFileOperationW needs file list with a double null termination */
2760 thisArg[lstrlenW(thisArg) + 1] = 0x00;
2762 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
2767 /* Handle no valid args */
2768 if (argsProcessed == 0) {
2769 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2775 /****************************************************************************
2781 void WCMD_rename (void)
2785 WIN32_FIND_DATAW fd;
2786 WCHAR input[MAX_PATH];
2787 WCHAR *dotDst = NULL;
2789 WCHAR dir[MAX_PATH];
2790 WCHAR fname[MAX_PATH];
2791 WCHAR ext[MAX_PATH];
2795 /* Must be at least two args */
2796 if (param1[0] == 0x00 || param2[0] == 0x00) {
2797 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2802 /* Destination cannot contain a drive letter or directory separator */
2803 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
2804 SetLastError(ERROR_INVALID_PARAMETER);
2810 /* Convert partial path to full path */
2811 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2812 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2813 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
2814 dotDst = strchrW(param2, '.');
2816 /* Split into components */
2817 WCMD_splitpath(input, drive, dir, fname, ext);
2819 hff = FindFirstFileW(input, &fd);
2820 if (hff == INVALID_HANDLE_VALUE)
2824 WCHAR dest[MAX_PATH];
2825 WCHAR src[MAX_PATH];
2826 WCHAR *dotSrc = NULL;
2829 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2831 /* FIXME: If dest name or extension is *, replace with filename/ext
2832 part otherwise use supplied name. This supports:
2834 ren jim.* fred.* etc
2835 However, windows has a more complex algorithm supporting eg
2836 ?'s and *'s mid name */
2837 dotSrc = strchrW(fd.cFileName, '.');
2839 /* Build src & dest name */
2840 strcpyW(src, drive);
2843 dirLen = strlenW(src);
2844 strcatW(src, fd.cFileName);
2847 if (param2[0] == '*') {
2848 strcatW(dest, fd.cFileName);
2849 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
2851 strcatW(dest, param2);
2852 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
2855 /* Build Extension */
2856 if (dotDst && (*(dotDst+1)=='*')) {
2857 if (dotSrc) strcatW(dest, dotSrc);
2858 } else if (dotDst) {
2859 if (dotDst) strcatW(dest, dotDst);
2862 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2863 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2865 status = MoveFileW(src, dest);
2868 WCMD_print_error ();
2871 } while (FindNextFileW(hff, &fd) != 0);
2876 /*****************************************************************************
2879 * Make a copy of the environment.
2881 static WCHAR *WCMD_dupenv( const WCHAR *env )
2891 len += (strlenW(&env[len]) + 1);
2893 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
2896 WINE_ERR("out of memory\n");
2899 memcpy (env_copy, env, len*sizeof (WCHAR));
2905 /*****************************************************************************
2908 * setlocal pushes the environment onto a stack
2909 * Save the environment as unicode so we don't screw anything up.
2911 void WCMD_setlocal (const WCHAR *s) {
2913 struct env_stack *env_copy;
2914 WCHAR cwd[MAX_PATH];
2916 /* setlocal does nothing outside of batch programs */
2917 if (!context) return;
2919 /* DISABLEEXTENSIONS ignored */
2921 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2924 WINE_ERR ("out of memory\n");
2928 env = GetEnvironmentStringsW ();
2929 env_copy->strings = WCMD_dupenv (env);
2930 if (env_copy->strings)
2932 env_copy->batchhandle = context->h;
2933 env_copy->next = saved_environment;
2934 saved_environment = env_copy;
2936 /* Save the current drive letter */
2937 GetCurrentDirectoryW(MAX_PATH, cwd);
2938 env_copy->u.cwd = cwd[0];
2941 LocalFree (env_copy);
2943 FreeEnvironmentStringsW (env);
2947 /*****************************************************************************
2950 * endlocal pops the environment off a stack
2951 * Note: When searching for '=', search from WCHAR position 1, to handle
2952 * special internal environment variables =C:, =D: etc
2954 void WCMD_endlocal (void) {
2955 WCHAR *env, *old, *p;
2956 struct env_stack *temp;
2959 /* setlocal does nothing outside of batch programs */
2960 if (!context) return;
2962 /* setlocal needs a saved environment from within the same context (batch
2963 program) as it was saved in */
2964 if (!saved_environment || saved_environment->batchhandle != context->h)
2967 /* pop the old environment from the stack */
2968 temp = saved_environment;
2969 saved_environment = temp->next;
2971 /* delete the current environment, totally */
2972 env = GetEnvironmentStringsW ();
2973 old = WCMD_dupenv (env);
2976 n = strlenW(&old[len]) + 1;
2977 p = strchrW(&old[len] + 1, '=');
2981 SetEnvironmentVariableW (&old[len], NULL);
2986 FreeEnvironmentStringsW (env);
2988 /* restore old environment */
2989 env = temp->strings;
2992 n = strlenW(&env[len]) + 1;
2993 p = strchrW(&env[len] + 1, '=');
2997 SetEnvironmentVariableW (&env[len], p);
3002 /* Restore current drive letter */
3003 if (IsCharAlphaW(temp->u.cwd)) {
3005 WCHAR cwd[MAX_PATH];
3006 static const WCHAR fmt[] = {'=','%','c',':','\0'};
3008 wsprintfW(envvar, fmt, temp->u.cwd);
3009 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3010 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3011 SetCurrentDirectoryW(cwd);
3019 /*****************************************************************************
3020 * WCMD_setshow_default
3022 * Set/Show the current default directory
3025 void WCMD_setshow_default (const WCHAR *args) {
3031 WIN32_FIND_DATAW fd;
3033 static const WCHAR parmD[] = {'/','D','\0'};
3035 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3037 /* Skip /D and trailing whitespace if on the front of the command line */
3038 if (CompareStringW(LOCALE_USER_DEFAULT,
3039 NORM_IGNORECASE | SORT_STRINGSORT,
3040 args, 2, parmD, -1) == CSTR_EQUAL) {
3042 while (*args && (*args==' ' || *args=='\t'))
3046 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3047 if (strlenW(args) == 0) {
3048 strcatW (cwd, newlineW);
3049 WCMD_output_asis (cwd);
3052 /* Remove any double quotes, which may be in the
3053 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3056 if (*args != '"') *pos++ = *args;
3059 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3063 /* Search for appropriate directory */
3064 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3065 hff = FindFirstFileW(string, &fd);
3066 if (hff != INVALID_HANDLE_VALUE) {
3068 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3069 WCHAR fpath[MAX_PATH];
3071 WCHAR dir[MAX_PATH];
3072 WCHAR fname[MAX_PATH];
3073 WCHAR ext[MAX_PATH];
3074 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3076 /* Convert path into actual directory spec */
3077 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3078 WCMD_splitpath(fpath, drive, dir, fname, ext);
3081 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3084 } while (FindNextFileW(hff, &fd) != 0);
3088 /* Change to that directory */
3089 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3091 status = SetCurrentDirectoryW(string);
3094 WCMD_print_error ();
3098 /* Save away the actual new directory, to store as current location */
3099 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3101 /* Restore old directory if drive letter would change, and
3102 CD x:\directory /D (or pushd c:\directory) not supplied */
3103 if ((strstrW(quals, parmD) == NULL) &&
3104 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3105 SetCurrentDirectoryW(cwd);
3109 /* Set special =C: type environment variable, for drive letter of
3110 change of directory, even if path was restored due to missing
3111 /D (allows changing drive letter when not resident on that
3113 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3115 strcpyW(env, equalW);
3116 memcpy(env+1, string, 2 * sizeof(WCHAR));
3118 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3119 SetEnvironmentVariableW(env, string);
3126 /****************************************************************************
3129 * Set/Show the system date
3130 * FIXME: Can't change date yet
3133 void WCMD_setshow_date (void) {
3135 WCHAR curdate[64], buffer[64];
3137 static const WCHAR parmT[] = {'/','T','\0'};
3139 if (strlenW(param1) == 0) {
3140 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3141 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3142 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3143 if (strstrW (quals, parmT) == NULL) {
3144 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3145 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3147 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3151 else WCMD_print_error ();
3154 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3158 /****************************************************************************
3160 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3163 static int WCMD_compare( const void *a, const void *b )
3166 const WCHAR * const *str_a = a, * const *str_b = b;
3167 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3168 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3169 if( r == CSTR_LESS_THAN ) return -1;
3170 if( r == CSTR_GREATER_THAN ) return 1;
3174 /****************************************************************************
3175 * WCMD_setshow_sortenv
3177 * sort variables into order for display
3178 * Optionally only display those who start with a stub
3179 * returns the count displayed
3181 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3183 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3186 if (stub) stublen = strlenW(stub);
3188 /* count the number of strings, and the total length */
3190 len += (strlenW(&s[len]) + 1);
3194 /* add the strings to an array */
3195 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3199 for( i=1; i<count; i++ )
3200 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3202 /* sort the array */
3203 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3206 for( i=0; i<count; i++ ) {
3207 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3208 NORM_IGNORECASE | SORT_STRINGSORT,
3209 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3210 /* Don't display special internal variables */
3211 if (str[i][0] != '=') {
3212 WCMD_output_asis(str[i]);
3213 WCMD_output_asis(newlineW);
3220 return displayedcount;
3223 /****************************************************************************
3226 * Set/Show the environment variables
3229 void WCMD_setshow_env (WCHAR *s) {
3234 static const WCHAR parmP[] = {'/','P','\0'};
3236 if (param1[0] == 0x00 && quals[0] == 0x00) {
3237 env = GetEnvironmentStringsW();
3238 WCMD_setshow_sortenv( env, NULL );
3242 /* See if /P supplied, and if so echo the prompt, and read in a reply */
3243 if (CompareStringW(LOCALE_USER_DEFAULT,
3244 NORM_IGNORECASE | SORT_STRINGSORT,
3245 s, 2, parmP, -1) == CSTR_EQUAL) {
3246 WCHAR string[MAXSTRING];
3250 while (*s && (*s==' ' || *s=='\t')) s++;
3252 WCMD_strip_quotes(s);
3254 /* If no parameter, or no '=' sign, return an error */
3255 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
3256 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3260 /* Output the prompt */
3262 if (strlenW(p) != 0) WCMD_output_asis(p);
3264 /* Read the reply */
3265 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3267 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3268 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3269 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3270 wine_dbgstr_w(string));
3271 status = SetEnvironmentVariableW(s, string);
3278 WCMD_strip_quotes(s);
3279 p = strchrW (s, '=');
3281 env = GetEnvironmentStringsW();
3282 if (WCMD_setshow_sortenv( env, s ) == 0) {
3283 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
3290 if (strlenW(p) == 0) p = NULL;
3291 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3293 status = SetEnvironmentVariableW(s, p);
3294 gle = GetLastError();
3295 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
3297 } else if ((!status)) WCMD_print_error();
3298 else errorlevel = 0;
3302 /****************************************************************************
3305 * Set/Show the path environment variable
3308 void WCMD_setshow_path (const WCHAR *args) {
3312 static const WCHAR pathW[] = {'P','A','T','H','\0'};
3313 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
3315 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
3316 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
3318 WCMD_output_asis ( pathEqW);
3319 WCMD_output_asis ( string);
3320 WCMD_output_asis ( newlineW);
3323 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
3327 if (*args == '=') args++; /* Skip leading '=' */
3328 status = SetEnvironmentVariableW(pathW, args);
3329 if (!status) WCMD_print_error();
3333 /****************************************************************************
3334 * WCMD_setshow_prompt
3336 * Set or show the command prompt.
3339 void WCMD_setshow_prompt (void) {
3342 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
3344 if (strlenW(param1) == 0) {
3345 SetEnvironmentVariableW(promptW, NULL);
3349 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
3350 if (strlenW(s) == 0) {
3351 SetEnvironmentVariableW(promptW, NULL);
3353 else SetEnvironmentVariableW(promptW, s);
3357 /****************************************************************************
3360 * Set/Show the system time
3361 * FIXME: Can't change time yet
3364 void WCMD_setshow_time (void) {
3366 WCHAR curtime[64], buffer[64];
3369 static const WCHAR parmT[] = {'/','T','\0'};
3371 if (strlenW(param1) == 0) {
3373 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
3374 curtime, sizeof(curtime)/sizeof(WCHAR))) {
3375 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
3376 if (strstrW (quals, parmT) == NULL) {
3377 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
3378 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3380 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3384 else WCMD_print_error ();
3387 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3391 /****************************************************************************
3394 * Shift batch parameters.
3395 * Optional /n says where to start shifting (n=0-8)
3398 void WCMD_shift (const WCHAR *args) {
3401 if (context != NULL) {
3402 WCHAR *pos = strchrW(args, '/');
3407 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
3408 start = (*(pos+1) - '0');
3410 SetLastError(ERROR_INVALID_PARAMETER);
3415 WINE_TRACE("Shifting variables, starting at %d\n", start);
3416 for (i=start;i<=8;i++) {
3417 context -> shift_count[i] = context -> shift_count[i+1] + 1;
3419 context -> shift_count[9] = context -> shift_count[9] + 1;
3424 /****************************************************************************
3427 void WCMD_start(const WCHAR *args)
3429 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
3430 '\\','s','t','a','r','t','.','e','x','e',0};
3431 WCHAR file[MAX_PATH];
3434 PROCESS_INFORMATION pi;
3436 GetWindowsDirectoryW( file, MAX_PATH );
3437 strcatW( file, exeW );
3438 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
3439 strcpyW( cmdline, file );
3440 strcatW( cmdline, spaceW );
3441 strcatW( cmdline, args );
3443 memset( &st, 0, sizeof(STARTUPINFOW) );
3444 st.cb = sizeof(STARTUPINFOW);
3446 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
3448 WaitForSingleObject( pi.hProcess, INFINITE );
3449 GetExitCodeProcess( pi.hProcess, &errorlevel );
3450 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
3451 CloseHandle(pi.hProcess);
3452 CloseHandle(pi.hThread);
3456 SetLastError(ERROR_FILE_NOT_FOUND);
3457 WCMD_print_error ();
3460 HeapFree( GetProcessHeap(), 0, cmdline );
3463 /****************************************************************************
3466 * Set the console title
3468 void WCMD_title (const WCHAR *args) {
3469 SetConsoleTitleW(args);
3472 /****************************************************************************
3475 * Copy a file to standard output.
3478 void WCMD_type (WCHAR *args) {
3482 BOOL writeHeaders = FALSE;
3484 if (param1[0] == 0x00) {
3485 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3489 if (param2[0] != 0x00) writeHeaders = TRUE;
3491 /* Loop through all args */
3494 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3502 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3503 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3504 FILE_ATTRIBUTE_NORMAL, NULL);
3505 if (h == INVALID_HANDLE_VALUE) {
3506 WCMD_print_error ();
3507 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3511 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
3512 WCMD_output(fmt, thisArg);
3514 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
3515 if (count == 0) break; /* ReadFile reports success on EOF! */
3517 WCMD_output_asis (buffer);
3524 /****************************************************************************
3527 * Output either a file or stdin to screen in pages
3530 void WCMD_more (WCHAR *args) {
3535 WCHAR moreStrPage[100];
3538 static const WCHAR moreStart[] = {'-','-',' ','\0'};
3539 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
3540 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
3541 ')',' ','-','-','\n','\0'};
3542 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
3544 /* Prefix the NLS more with '-- ', then load the text */
3546 strcpyW(moreStr, moreStart);
3547 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
3548 (sizeof(moreStr)/sizeof(WCHAR))-3);
3550 if (param1[0] == 0x00) {
3552 /* Wine implements pipes via temporary files, and hence stdin is
3553 effectively reading from the file. This means the prompts for
3554 more are satisfied by the next line from the input (file). To
3555 avoid this, ensure stdin is to the console */
3556 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
3557 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
3558 FILE_SHARE_READ, NULL, OPEN_EXISTING,
3559 FILE_ATTRIBUTE_NORMAL, 0);
3560 WINE_TRACE("No parms - working probably in pipe mode\n");
3561 SetStdHandle(STD_INPUT_HANDLE, hConIn);
3563 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
3564 once you get in this bit unless due to a pipe, its going to end badly... */
3565 wsprintfW(moreStrPage, moreFmt, moreStr);
3567 WCMD_enter_paged_mode(moreStrPage);
3568 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3569 if (count == 0) break; /* ReadFile reports success on EOF! */
3571 WCMD_output_asis (buffer);
3573 WCMD_leave_paged_mode();
3575 /* Restore stdin to what it was */
3576 SetStdHandle(STD_INPUT_HANDLE, hstdin);
3577 CloseHandle(hConIn);
3581 BOOL needsPause = FALSE;
3583 /* Loop through all args */
3584 WINE_TRACE("Parms supplied - working through each file\n");
3585 WCMD_enter_paged_mode(moreStrPage);
3588 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3596 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
3597 WCMD_leave_paged_mode();
3598 WCMD_output_asis(moreStrPage);
3599 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3600 WCMD_enter_paged_mode(moreStrPage);
3604 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3605 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3606 FILE_ATTRIBUTE_NORMAL, NULL);
3607 if (h == INVALID_HANDLE_VALUE) {
3608 WCMD_print_error ();
3609 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3613 ULONG64 fileLen = 0;
3614 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
3616 /* Get the file size */
3617 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
3618 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
3621 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3622 if (count == 0) break; /* ReadFile reports success on EOF! */
3626 /* Update % count (would be used in WCMD_output_asis as prompt) */
3627 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
3629 WCMD_output_asis (buffer);
3635 WCMD_leave_paged_mode();
3639 /****************************************************************************
3642 * Display verify flag.
3643 * FIXME: We don't actually do anything with the verify flag other than toggle
3647 void WCMD_verify (const WCHAR *args) {
3651 count = strlenW(args);
3653 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
3654 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
3657 if (lstrcmpiW(args, onW) == 0) {
3661 else if (lstrcmpiW(args, offW) == 0) {
3662 verify_mode = FALSE;
3665 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
3668 /****************************************************************************
3671 * Display version info.
3674 void WCMD_version (void) {
3676 WCMD_output_asis (version_string);
3680 /****************************************************************************
3683 * Display volume information (set_label = FALSE)
3684 * Additionally set volume label (set_label = TRUE)
3685 * Returns 1 on success, 0 otherwise
3688 int WCMD_volume(BOOL set_label, const WCHAR *path)
3690 DWORD count, serial;
3691 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
3694 if (strlenW(path) == 0) {
3695 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
3697 WCMD_print_error ();
3700 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
3701 &serial, NULL, NULL, NULL, 0);
3704 static const WCHAR fmt[] = {'%','s','\\','\0'};
3705 if ((path[1] != ':') || (strlenW(path) != 2)) {
3706 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
3709 wsprintfW (curdir, fmt, path);
3710 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
3715 WCMD_print_error ();
3718 if (label[0] != '\0') {
3719 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
3723 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
3726 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
3727 HIWORD(serial), LOWORD(serial));
3729 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
3730 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3732 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3733 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3735 if (strlenW(path) != 0) {
3736 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
3739 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
3745 /**************************************************************************
3748 * Exit either the process, or just this batch program
3752 void WCMD_exit (CMD_LIST **cmdList) {
3754 static const WCHAR parmB[] = {'/','B','\0'};
3755 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
3757 if (context && lstrcmpiW(quals, parmB) == 0) {
3759 context -> skip_rest = TRUE;
3767 /*****************************************************************************
3770 * Lists or sets file associations (assoc = TRUE)
3771 * Lists or sets file types (assoc = FALSE)
3773 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
3776 DWORD accessOptions = KEY_READ;
3778 LONG rc = ERROR_SUCCESS;
3779 WCHAR keyValue[MAXSTRING];
3780 DWORD valueLen = MAXSTRING;
3782 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
3783 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
3785 /* See if parameter includes '=' */
3787 newValue = strchrW(args, '=');
3788 if (newValue) accessOptions |= KEY_WRITE;
3790 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
3791 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
3792 accessOptions, &key) != ERROR_SUCCESS) {
3793 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
3797 /* If no parameters then list all associations */
3798 if (*args == 0x00) {
3801 /* Enumerate all the keys */
3802 while (rc != ERROR_NO_MORE_ITEMS) {
3803 WCHAR keyName[MAXSTRING];
3806 /* Find the next value */
3807 nameLen = MAXSTRING;
3808 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
3810 if (rc == ERROR_SUCCESS) {
3812 /* Only interested in extension ones if assoc, or others
3814 if ((keyName[0] == '.' && assoc) ||
3815 (!(keyName[0] == '.') && (!assoc)))
3817 WCHAR subkey[MAXSTRING];
3818 strcpyW(subkey, keyName);
3819 if (!assoc) strcatW(subkey, shOpCmdW);
3821 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3823 valueLen = sizeof(keyValue)/sizeof(WCHAR);
3824 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3825 WCMD_output_asis(keyName);
3826 WCMD_output_asis(equalW);
3827 /* If no default value found, leave line empty after '=' */
3828 if (rc == ERROR_SUCCESS) {
3829 WCMD_output_asis(keyValue);
3831 WCMD_output_asis(newlineW);
3832 RegCloseKey(readKey);
3840 /* Parameter supplied - if no '=' on command line, its a query */
3841 if (newValue == NULL) {
3843 WCHAR subkey[MAXSTRING];
3845 /* Query terminates the parameter at the first space */
3846 strcpyW(keyValue, args);
3847 space = strchrW(keyValue, ' ');
3848 if (space) *space=0x00;
3850 /* Set up key name */
3851 strcpyW(subkey, keyValue);
3852 if (!assoc) strcatW(subkey, shOpCmdW);
3854 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3856 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3857 WCMD_output_asis(args);
3858 WCMD_output_asis(equalW);
3859 /* If no default value found, leave line empty after '=' */
3860 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
3861 WCMD_output_asis(newlineW);
3862 RegCloseKey(readKey);
3865 WCHAR msgbuffer[MAXSTRING];
3867 /* Load the translated 'File association not found' */
3869 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3871 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3873 WCMD_output_stderr(msgbuffer, keyValue);
3877 /* Not a query - its a set or clear of a value */
3880 WCHAR subkey[MAXSTRING];
3882 /* Get pointer to new value */
3886 /* Set up key name */
3887 strcpyW(subkey, args);
3888 if (!assoc) strcatW(subkey, shOpCmdW);
3890 /* If nothing after '=' then clear value - only valid for ASSOC */
3891 if (*newValue == 0x00) {
3893 if (assoc) rc = RegDeleteKeyW(key, args);
3894 if (assoc && rc == ERROR_SUCCESS) {
3895 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
3897 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
3902 WCHAR msgbuffer[MAXSTRING];
3904 /* Load the translated 'File association not found' */
3906 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
3907 sizeof(msgbuffer)/sizeof(WCHAR));
3909 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
3910 sizeof(msgbuffer)/sizeof(WCHAR));
3912 WCMD_output_stderr(msgbuffer, keyValue);
3916 /* It really is a set value = contents */
3918 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
3919 accessOptions, NULL, &readKey, NULL);
3920 if (rc == ERROR_SUCCESS) {
3921 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
3923 sizeof(WCHAR) * (strlenW(newValue) + 1));
3924 RegCloseKey(readKey);
3927 if (rc != ERROR_SUCCESS) {
3931 WCMD_output_asis(args);
3932 WCMD_output_asis(equalW);
3933 WCMD_output_asis(newValue);
3934 WCMD_output_asis(newlineW);
3944 /****************************************************************************
3947 * Colors the terminal screen.
3950 void WCMD_color (void) {
3952 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3953 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3955 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3956 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3960 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3966 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3971 /* Convert the color hex digits */
3972 if (param1[0] == 0x00) {
3973 color = defaultColor;
3975 color = strtoulW(param1, NULL, 16);
3978 /* Fail if fg == bg color */
3979 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3984 /* Set the current screen contents and ensure all future writes
3985 remain this color */
3986 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3987 SetConsoleTextAttribute(hStdOut, color);