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 deviceW[] = {'\\','\\','.','\\','\0'};
51 const WCHAR inbuilt[][10] = {
52 {'C','A','L','L','\0'},
54 {'C','H','D','I','R','\0'},
56 {'C','O','P','Y','\0'},
57 {'C','T','T','Y','\0'},
58 {'D','A','T','E','\0'},
61 {'E','C','H','O','\0'},
62 {'E','R','A','S','E','\0'},
64 {'G','O','T','O','\0'},
65 {'H','E','L','P','\0'},
67 {'L','A','B','E','L','\0'},
69 {'M','K','D','I','R','\0'},
70 {'M','O','V','E','\0'},
71 {'P','A','T','H','\0'},
72 {'P','A','U','S','E','\0'},
73 {'P','R','O','M','P','T','\0'},
76 {'R','E','N','A','M','E','\0'},
78 {'R','M','D','I','R','\0'},
80 {'S','H','I','F','T','\0'},
81 {'S','T','A','R','T','\0'},
82 {'T','I','M','E','\0'},
83 {'T','I','T','L','E','\0'},
84 {'T','Y','P','E','\0'},
85 {'V','E','R','I','F','Y','\0'},
88 {'E','N','D','L','O','C','A','L','\0'},
89 {'S','E','T','L','O','C','A','L','\0'},
90 {'P','U','S','H','D','\0'},
91 {'P','O','P','D','\0'},
92 {'A','S','S','O','C','\0'},
93 {'C','O','L','O','R','\0'},
94 {'F','T','Y','P','E','\0'},
95 {'M','O','R','E','\0'},
96 {'C','H','O','I','C','E','\0'},
97 {'E','X','I','T','\0'}
99 static const WCHAR externals[][10] = {
100 {'A','T','T','R','I','B','\0'},
101 {'X','C','O','P','Y','\0'}
103 static const WCHAR fslashW[] = {'/','\0'};
104 static const WCHAR onW[] = {'O','N','\0'};
105 static const WCHAR offW[] = {'O','F','F','\0'};
106 static const WCHAR parmY[] = {'/','Y','\0'};
107 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
108 static const WCHAR eqeqW[] = {'=','=','\0'};
110 static HINSTANCE hinst;
111 struct env_stack *saved_environment;
112 static BOOL verify_mode = FALSE;
114 /**************************************************************************
117 * Issue a message and ask for confirmation, waiting on a valid answer.
119 * Returns True if Y (or A) answer is selected
120 * If optionAll contains a pointer, ALL is allowed, and if answered
124 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
128 WCHAR confirm[MAXSTRING];
129 WCHAR options[MAXSTRING];
130 WCHAR Ybuffer[MAXSTRING];
131 WCHAR Nbuffer[MAXSTRING];
132 WCHAR Abuffer[MAXSTRING];
133 WCHAR answer[MAX_PATH] = {'\0'};
136 /* Load the translated valid answers */
138 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
139 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
140 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
141 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
142 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
143 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
145 /* Loop waiting on a valid answer */
150 WCMD_output_asis (message);
152 WCMD_output_asis (confirm);
153 WCMD_output_asis (options);
154 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
155 answer[0] = toupperW(answer[0]);
156 if (answer[0] == Ybuffer[0])
158 if (answer[0] == Nbuffer[0])
160 if (optionAll && answer[0] == Abuffer[0])
168 /****************************************************************************
171 * Clear the terminal screen.
174 void WCMD_clear_screen (void) {
176 /* Emulate by filling the screen from the top left to bottom right with
177 spaces, then moving the cursor to the top left afterwards */
178 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
179 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
181 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
186 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
190 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
191 SetConsoleCursorPosition(hStdOut, topLeft);
195 /****************************************************************************
198 * Change the default i/o device (ie redirect STDin/STDout).
201 void WCMD_change_tty (void) {
203 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
207 /****************************************************************************
212 void WCMD_choice (const WCHAR * args) {
214 static const WCHAR bellW[] = {7,0};
215 static const WCHAR commaW[] = {',',0};
216 static const WCHAR bracket_open[] = {'[',0};
217 static const WCHAR bracket_close[] = {']','?',0};
222 WCHAR *my_command = NULL;
223 WCHAR opt_default = 0;
224 DWORD opt_timeout = 0;
231 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
234 my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
236 ptr = WCMD_skip_leading_spaces(my_command);
237 while (*ptr == '/') {
238 switch (toupperW(ptr[1])) {
241 /* the colon is optional */
245 if (!*ptr || isspaceW(*ptr)) {
246 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
247 heap_free(my_command);
251 /* remember the allowed keys (overwrite previous /C option) */
253 while (*ptr && (!isspaceW(*ptr)))
257 /* terminate allowed chars */
259 ptr = WCMD_skip_leading_spaces(&ptr[1]);
261 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
266 ptr = WCMD_skip_leading_spaces(&ptr[2]);
271 ptr = WCMD_skip_leading_spaces(&ptr[2]);
276 /* the colon is optional */
280 opt_default = *ptr++;
282 if (!opt_default || (*ptr != ',')) {
283 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
284 heap_free(my_command);
290 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
296 opt_timeout = atoiW(answer);
298 ptr = WCMD_skip_leading_spaces(ptr);
302 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
303 heap_free(my_command);
309 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
312 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
314 /* use default keys, when needed: localized versions of "Y"es and "No" */
316 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
317 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
322 /* print the question, when needed */
324 WCMD_output_asis(ptr);
328 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
332 /* print a list of all allowed answers inside brackets */
333 WCMD_output_asis(bracket_open);
336 while ((answer[0] = *ptr++)) {
337 WCMD_output_asis(answer);
339 WCMD_output_asis(commaW);
341 WCMD_output_asis(bracket_close);
346 /* FIXME: Add support for option /T */
347 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
350 answer[0] = toupperW(answer[0]);
352 ptr = strchrW(opt_c, answer[0]);
354 WCMD_output_asis(answer);
355 WCMD_output_asis(newlineW);
357 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
359 errorlevel = (ptr - opt_c) + 1;
360 WINE_TRACE("answer: %d\n", errorlevel);
361 heap_free(my_command);
366 /* key not allowed: play the bell */
367 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
368 WCMD_output_asis(bellW);
373 /****************************************************************************
376 * Adds an EOF onto the end of a file
377 * Returns TRUE on success
379 static BOOL WCMD_AppendEOF(WCHAR *filename)
385 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
386 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
387 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
390 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
393 SetFilePointer (h, 0, NULL, FILE_END);
394 if (!WriteFile(h, &eof, 1, NULL, NULL)) {
395 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
404 /****************************************************************************
408 * optionally reading only until EOF (ascii copy)
409 * optionally appending onto an existing file (append)
410 * Returns TRUE on success
412 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
416 DWORD bytesread, byteswritten;
418 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
419 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
421 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
422 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
424 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
428 /* Open the output file, overwriting if not appending */
429 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
430 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
432 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
437 /* Move to end of destination if we are going to append to it */
439 SetFilePointer(out, 0, NULL, FILE_END);
442 /* Loop copying data from source to destination until EOF read */
446 char buffer[MAXSTRING];
448 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
451 /* Stop at first EOF */
453 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
454 if (ptr) bytesread = (ptr - buffer);
458 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
459 if (!ok || byteswritten != bytesread) {
460 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
461 wine_dbgstr_w(dstname), GetLastError());
465 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
466 wine_dbgstr_w(srcname), GetLastError());
468 } while (ok && bytesread > 0);
475 /****************************************************************************
478 * Copy a file or wildcarded set.
479 * For ascii/binary type copies, it gets complex:
480 * Syntax on command line is
481 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
482 * Where first /a or /b sets 'mode in operation' until another is found
483 * once another is found, it applies to the file preceding the /a or /b
484 * In addition each filename can contain wildcards
485 * To make matters worse, the + may be in the same parameter (i.e. no
486 * whitespace) or with whitespace separating it
488 * ASCII mode on read == read and stop at first EOF
489 * ASCII mode on write == append EOF to destination
490 * Binary == copy as-is
492 * Design of this is to build up a list of files which will be copied into a
493 * list, then work through the list file by file.
494 * If no destination is specified, it defaults to the name of the first file in
495 * the list, but the current directory.
499 void WCMD_copy(WCHAR * args) {
501 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
506 HANDLE hff = INVALID_HANDLE_VALUE;
507 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
508 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
509 BOOL anyconcats = FALSE; /* Have we found any + options */
510 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
511 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
512 BOOL prompt; /* Prompt before overwriting */
513 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
514 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
518 BOOL dstisdevice = FALSE;
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 = heap_alloc(sizeof(COPY_FILES));
637 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
638 thiscopy->concatenate = concatnextfilename;
639 thiscopy->binarycopy = binarymode;
640 thiscopy->next = NULL;
642 /* Time to work out the name. Allocate at least enough space (deliberately too much to
643 leave space to append \* to the end) , then copy in character by character. Strip off
644 quotes if we find them. */
645 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
646 thiscopy->name = heap_alloc(len*sizeof(WCHAR));
647 memset(thiscopy->name, 0x00, len);
650 pos2 = thiscopy->name;
652 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
654 inquotes = !inquotes;
656 } else *pos2++ = *pos1++;
659 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
661 /* This is either the first source, concatenated subsequent source or destination */
662 if (sourcelist == NULL) {
663 WINE_TRACE("Adding as first source part\n");
664 sourcelist = thiscopy;
665 lastcopyentry = thiscopy;
666 } else if (concatnextfilename) {
667 WINE_TRACE("Adding to source file list to be concatenated\n");
668 lastcopyentry->next = thiscopy;
669 lastcopyentry = thiscopy;
670 } else if (destination == NULL) {
671 destination = thiscopy;
673 /* We have processed sources and destinations and still found more to do - invalid */
674 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
678 concatnextfilename = FALSE;
680 /* We either need to process the rest of the parameter or move to the next */
681 if (*pos1 == '/' || *pos1 == '+') {
685 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
689 /* Ensure we have at least one source file */
691 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
696 /* Default whether automatic overwriting is on. If we are interactive then
697 we prompt by default, otherwise we overwrite by default
698 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
699 if (opt_noty) prompt = TRUE;
700 else if (opt_y) prompt = FALSE;
702 /* By default, we will force the overwrite in batch mode and ask for
703 * confirmation in interactive mode. */
704 prompt = interactive;
705 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
706 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
707 * default behavior. */
708 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
709 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
710 if (!lstrcmpiW (copycmd, parmY))
712 else if (!lstrcmpiW (copycmd, parmNoY))
717 /* Calculate the destination now - if none supplied, its current dir +
718 filename of first file in list*/
719 if (destination == NULL) {
721 WINE_TRACE("No destination supplied, so need to calculate it\n");
722 strcpyW(destname, dotW);
723 strcatW(destname, slashW);
725 destination = heap_alloc(sizeof(COPY_FILES));
726 if (destination == NULL) goto exitreturn;
727 destination->concatenate = FALSE; /* Not used for destination */
728 destination->binarycopy = binarymode;
729 destination->next = NULL; /* Not used for destination */
730 destination->name = NULL; /* To be filled in */
731 destisdirectory = TRUE;
737 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
739 /* Convert to fully qualified path/filename */
740 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
741 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
743 /* If parameter is a directory, ensure it ends in \ */
744 attributes = GetFileAttributesW(destname);
745 if ((destname[strlenW(destname) - 1] == '\\') ||
746 ((attributes != INVALID_FILE_ATTRIBUTES) &&
747 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
749 destisdirectory = TRUE;
750 if (!(destname[strlenW(destname) - 1] == '\\')) strcatW(destname, slashW);
751 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
755 /* Normally, the destination is the current directory unless we are
756 concatenating, in which case its current directory plus first filename.
758 In addition by default it is a binary copy unless concatenating, when
759 the copy defaults to an ascii copy (stop at EOF). We do not know the
760 first source part yet (until we search) so flag as needing filling in. */
763 /* We have found an a+b type syntax, so destination has to be a filename
764 and we need to default to ascii copying. If we have been supplied a
765 directory as the destination, we need to defer calculating the name */
766 if (destisdirectory) appendfirstsource = TRUE;
767 if (destination->binarycopy == -1) destination->binarycopy = 0;
769 } else if (!destisdirectory) {
770 /* We have been asked to copy to a filename. Default to ascii IF the
771 source contains wildcards (true even if only one match) */
772 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
773 anyconcats = TRUE; /* We really are concatenating to a single file */
774 if (destination->binarycopy == -1) {
775 destination->binarycopy = 0;
778 if (destination->binarycopy == -1) {
779 destination->binarycopy = 1;
784 /* Save away the destination name*/
785 heap_free(destination->name);
786 destination->name = heap_strdupW(destname);
787 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
788 wine_dbgstr_w(destname), appendfirstsource);
790 /* Remember if the destination is a device */
791 if (strncmpW(destination->name, deviceW, strlenW(deviceW)) == 0) {
792 WINE_TRACE("Destination is a device\n");
796 /* Now we need to walk the set of sources, and process each name we come to.
797 If anyconcats is true, we are writing to one file, otherwise we are using
798 the source name each time.
799 If destination exists, prompt for overwrite the first time (if concatenating
800 we ask each time until yes is answered)
801 The first source file we come across must exist (when wildcards expanded)
802 and if concatenating with overwrite prompts, each source file must exist
803 until a yes is answered. */
805 thiscopy = sourcelist;
808 while (thiscopy != NULL) {
810 WCHAR srcpath[MAX_PATH];
811 const WCHAR *srcname;
814 BOOL srcisdevice = FALSE;
816 /* If it was not explicit, we now know whether we are concatenating or not and
817 hence whether to copy as binary or ascii */
818 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
820 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
821 to where the filename portion begins (used for wildcart expansion. */
822 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
823 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
825 /* If parameter is a directory, ensure it ends in \* */
826 attributes = GetFileAttributesW(srcpath);
827 if (srcpath[strlenW(srcpath) - 1] == '\\') {
829 /* We need to know where the filename part starts, so append * and
830 recalculate the full resulting path */
831 strcatW(thiscopy->name, starW);
832 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
833 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
835 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
836 (attributes != INVALID_FILE_ATTRIBUTES) &&
837 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
839 /* We need to know where the filename part starts, so append \* and
840 recalculate the full resulting path */
841 strcatW(thiscopy->name, slashstarW);
842 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
843 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
846 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
847 wine_dbgstr_w(srcpath), anyconcats);
849 /* If the source is a device, just use it, otherwise search */
850 if (strncmpW(srcpath, deviceW, strlenW(deviceW)) == 0) {
851 WINE_TRACE("Source is a device\n");
853 srcname = &srcpath[4]; /* After the \\.\ prefix */
856 /* Loop through all source files */
857 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
858 hff = FindFirstFileW(srcpath, &fd);
859 if (hff != INVALID_HANDLE_VALUE) {
860 srcname = fd.cFileName;
864 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
866 WCHAR outname[MAX_PATH];
869 /* Skip . and .., and directories */
870 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
871 WINE_TRACE("Skipping directories\n");
874 /* Build final destination name */
875 strcpyW(outname, destination->name);
876 if (destisdirectory || appendfirstsource) strcatW(outname, srcname);
878 /* Build source name */
879 if (!srcisdevice) strcpyW(filenamepart, srcname);
881 /* Do we just overwrite (we do if we are writing to a device) */
883 if (dstisdevice || (anyconcats && writtenoneconcat)) {
887 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
888 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
889 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
890 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
892 /* Prompt before overwriting */
894 DWORD attributes = GetFileAttributesW(outname);
895 if (attributes != INVALID_FILE_ATTRIBUTES) {
897 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
898 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
901 else overwrite = TRUE;
904 /* If we needed to save away the first filename, do it */
905 if (appendfirstsource && overwrite) {
906 heap_free(destination->name);
907 destination->name = heap_strdupW(outname);
908 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
909 appendfirstsource = FALSE;
910 destisdirectory = FALSE;
913 /* Do the copy as appropriate */
915 if (anyconcats && writtenoneconcat) {
916 if (thiscopy->binarycopy) {
917 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
919 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
921 } else if (!thiscopy->binarycopy) {
922 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
923 } else if (srcisdevice) {
924 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
926 status = CopyFileW(srcpath, outname, FALSE);
932 WINE_TRACE("Copied successfully\n");
933 if (anyconcats) writtenoneconcat = TRUE;
935 /* Append EOF if ascii destination and we are not going to add more onto the end
936 Note: Testing shows windows has an optimization whereas if you have a binary
937 copy of a file to a single destination (ie concatenation) then it does not add
938 the EOF, hence the check on the source copy type below. */
939 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
940 if (!WCMD_AppendEOF(outname)) {
948 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
949 if (!srcisdevice) FindClose (hff);
951 /* Error if the first file was not found */
952 if (!anyconcats || (anyconcats && !writtenoneconcat)) {
958 /* Step on to the next supplied source */
959 thiscopy = thiscopy -> next;
962 /* Append EOF if ascii destination and we were concatenating */
963 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
964 if (!WCMD_AppendEOF(destination->name)) {
970 /* Exit out of the routine, freeing any remaining allocated memory */
973 thiscopy = sourcelist;
974 while (thiscopy != NULL) {
976 /* Free up this block*/
977 thiscopy = thiscopy -> next;
978 heap_free(prevcopy->name);
982 /* Free up the destination memory */
984 heap_free(destination->name);
985 heap_free(destination);
991 /****************************************************************************
994 * Create a directory (and, if needed, any intermediate directories).
996 * Modifies its argument by replacing slashes temporarily with nulls.
999 static BOOL create_full_path(WCHAR* path)
1003 /* don't mess with drive letter portion of path, if any */
1008 /* Strip trailing slashes. */
1009 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
1012 /* Step through path, creating intermediate directories as needed. */
1013 /* First component includes drive letter, if any. */
1017 /* Skip to end of component */
1018 while (*p == '\\') p++;
1019 while (*p && *p != '\\') p++;
1021 /* path is now the original full path */
1022 return CreateDirectoryW(path, NULL);
1024 /* Truncate path, create intermediate directory, and restore path */
1026 rv = CreateDirectoryW(path, NULL);
1028 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1035 void WCMD_create_dir (WCHAR *args) {
1039 if (param1[0] == 0x00) {
1040 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1043 /* Loop through all args */
1045 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1047 if (!create_full_path(thisArg)) {
1048 WCMD_print_error ();
1054 /* Parse the /A options given by the user on the commandline
1055 * into a bitmask of wanted attributes (*wantSet),
1056 * and a bitmask of unwanted attributes (*wantClear).
1058 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1059 static const WCHAR parmA[] = {'/','A','\0'};
1062 /* both are strictly 'out' parameters */
1066 /* For each /A argument */
1067 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1068 /* Skip /A itself */
1071 /* Skip optional : */
1074 /* For each of the attribute specifier chars to this /A option */
1075 for (; *p != 0 && *p != '/'; p++) {
1076 BOOL negate = FALSE;
1084 /* Convert the attribute specifier to a bit in one of the masks */
1086 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1087 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1088 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1089 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1091 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1101 /* If filename part of parameter is * or *.*,
1102 * and neither /Q nor /P options were given,
1103 * prompt the user whether to proceed.
1104 * Returns FALSE if user says no, TRUE otherwise.
1105 * *pPrompted is set to TRUE if the user is prompted.
1106 * (If /P supplied, del will prompt for individual files later.)
1108 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1109 static const WCHAR parmP[] = {'/','P','\0'};
1110 static const WCHAR parmQ[] = {'/','Q','\0'};
1112 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1113 static const WCHAR anyExt[]= {'.','*','\0'};
1115 WCHAR dir[MAX_PATH];
1116 WCHAR fname[MAX_PATH];
1117 WCHAR ext[MAX_PATH];
1118 WCHAR fpath[MAX_PATH];
1120 /* Convert path into actual directory spec */
1121 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1122 WCMD_splitpath(fpath, drive, dir, fname, ext);
1124 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1125 if ((strcmpW(fname, starW) == 0) &&
1126 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1128 WCHAR question[MAXSTRING];
1129 static const WCHAR fmt[] = {'%','s',' ','\0'};
1131 /* Caller uses this to suppress "file not found" warning later */
1134 /* Ask for confirmation */
1135 wsprintfW(question, fmt, fpath);
1136 return WCMD_ask_confirm(question, TRUE, NULL);
1139 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1143 /* Helper function for WCMD_delete().
1144 * Deletes a single file, directory, or wildcard.
1145 * If /S was given, does it recursively.
1146 * Returns TRUE if a file was deleted.
1148 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1150 static const WCHAR parmP[] = {'/','P','\0'};
1151 static const WCHAR parmS[] = {'/','S','\0'};
1152 static const WCHAR parmF[] = {'/','F','\0'};
1154 DWORD unwanted_attrs;
1156 WCHAR argCopy[MAX_PATH];
1157 WIN32_FIND_DATAW fd;
1159 WCHAR fpath[MAX_PATH];
1161 BOOL handleParm = TRUE;
1163 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1165 strcpyW(argCopy, thisArg);
1166 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1167 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1169 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1170 /* Skip this arg if user declines to delete *.* */
1174 /* First, try to delete in the current directory */
1175 hff = FindFirstFileW(argCopy, &fd);
1176 if (hff == INVALID_HANDLE_VALUE) {
1182 /* Support del <dirname> by just deleting all files dirname\* */
1184 && (strchrW(argCopy,'*') == NULL)
1185 && (strchrW(argCopy,'?') == NULL)
1186 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1188 WCHAR modifiedParm[MAX_PATH];
1189 static const WCHAR slashStar[] = {'\\','*','\0'};
1191 strcpyW(modifiedParm, argCopy);
1192 strcatW(modifiedParm, slashStar);
1195 WCMD_delete_one(modifiedParm);
1197 } else if (handleParm) {
1199 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1200 strcpyW (fpath, argCopy);
1202 p = strrchrW (fpath, '\\');
1205 strcatW (fpath, fd.cFileName);
1207 else strcpyW (fpath, fd.cFileName);
1208 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1211 /* Handle attribute matching (/A) */
1212 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1213 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1215 /* /P means prompt for each file */
1216 if (ok && strstrW (quals, parmP) != NULL) {
1219 /* Ask for confirmation */
1220 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1221 ok = WCMD_ask_confirm(question, FALSE, NULL);
1222 LocalFree(question);
1225 /* Only proceed if ok to */
1228 /* If file is read only, and /A:r or /F supplied, delete it */
1229 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1230 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1231 strstrW (quals, parmF) != NULL)) {
1232 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1235 /* Now do the delete */
1236 if (!DeleteFileW(fpath)) WCMD_print_error ();
1240 } while (FindNextFileW(hff, &fd) != 0);
1244 /* Now recurse into all subdirectories handling the parameter in the same way */
1245 if (strstrW (quals, parmS) != NULL) {
1247 WCHAR thisDir[MAX_PATH];
1251 WCHAR dir[MAX_PATH];
1252 WCHAR fname[MAX_PATH];
1253 WCHAR ext[MAX_PATH];
1255 /* Convert path into actual directory spec */
1256 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1257 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1259 strcpyW(thisDir, drive);
1260 strcatW(thisDir, dir);
1261 cPos = strlenW(thisDir);
1263 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1265 /* Append '*' to the directory */
1266 thisDir[cPos] = '*';
1267 thisDir[cPos+1] = 0x00;
1269 hff = FindFirstFileW(thisDir, &fd);
1271 /* Remove residual '*' */
1272 thisDir[cPos] = 0x00;
1274 if (hff != INVALID_HANDLE_VALUE) {
1275 DIRECTORY_STACK *allDirs = NULL;
1276 DIRECTORY_STACK *lastEntry = NULL;
1279 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1280 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1281 (strcmpW(fd.cFileName, dotW) != 0)) {
1283 DIRECTORY_STACK *nextDir;
1284 WCHAR subParm[MAX_PATH];
1286 /* Work out search parameter in sub dir */
1287 strcpyW (subParm, thisDir);
1288 strcatW (subParm, fd.cFileName);
1289 strcatW (subParm, slashW);
1290 strcatW (subParm, fname);
1291 strcatW (subParm, ext);
1292 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1294 /* Allocate memory, add to list */
1295 nextDir = heap_alloc(sizeof(DIRECTORY_STACK));
1296 if (allDirs == NULL) allDirs = nextDir;
1297 if (lastEntry != NULL) lastEntry->next = nextDir;
1298 lastEntry = nextDir;
1299 nextDir->next = NULL;
1300 nextDir->dirName = heap_strdupW(subParm);
1302 } while (FindNextFileW(hff, &fd) != 0);
1305 /* Go through each subdir doing the delete */
1306 while (allDirs != NULL) {
1307 DIRECTORY_STACK *tempDir;
1309 tempDir = allDirs->next;
1310 found |= WCMD_delete_one (allDirs->dirName);
1312 heap_free(allDirs->dirName);
1322 /****************************************************************************
1325 * Delete a file or wildcarded set.
1328 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1329 * - Each set is a pattern, eg /ahr /as-r means
1330 * readonly+hidden OR nonreadonly system files
1331 * - The '-' applies to a single field, ie /a:-hr means read only
1335 BOOL WCMD_delete (WCHAR *args) {
1338 BOOL argsProcessed = FALSE;
1339 BOOL foundAny = FALSE;
1343 for (argno=0; ; argno++) {
1348 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1350 break; /* no more parameters */
1352 continue; /* skip options */
1354 argsProcessed = TRUE;
1355 found = WCMD_delete_one(thisArg);
1358 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1363 /* Handle no valid args */
1365 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1373 * Returns a trimmed version of s with all leading and trailing whitespace removed
1377 static WCHAR *WCMD_strtrim(const WCHAR *s)
1379 DWORD len = strlenW(s);
1380 const WCHAR *start = s;
1383 result = heap_alloc((len + 1) * sizeof(WCHAR));
1385 while (isspaceW(*start)) start++;
1387 const WCHAR *end = s + len - 1;
1388 while (end > start && isspaceW(*end)) end--;
1389 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1390 result[end - start + 1] = '\0';
1398 /****************************************************************************
1401 * Echo input to the screen (or not). We don't try to emulate the bugs
1402 * in DOS (try typing "ECHO ON AGAIN" for an example).
1405 void WCMD_echo (const WCHAR *args)
1408 const WCHAR *origcommand = args;
1411 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1412 || args[0]==':' || args[0]==';')
1415 trimmed = WCMD_strtrim(args);
1416 if (!trimmed) return;
1418 count = strlenW(trimmed);
1419 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1420 && origcommand[0]!=';') {
1421 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1422 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1427 if (lstrcmpiW(trimmed, onW) == 0)
1429 else if (lstrcmpiW(trimmed, offW) == 0)
1432 WCMD_output_asis (args);
1433 WCMD_output_asis (newlineW);
1438 /*****************************************************************************
1441 * Execute a command, and any && or bracketed follow on to the command. The
1442 * first command to be executed may not be at the front of the
1443 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1445 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1446 BOOL isIF, BOOL executecmds)
1448 CMD_LIST *curPosition = *cmdList;
1449 int myDepth = (*cmdList)->bracketDepth;
1451 WINE_TRACE("cmdList(%p), firstCmd(%p), doIt(%d)\n",
1452 cmdList, wine_dbgstr_w(firstcmd),
1455 /* Skip leading whitespace between condition and the command */
1456 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1458 /* Process the first command, if there is one */
1459 if (executecmds && firstcmd && *firstcmd) {
1460 WCHAR *command = heap_strdupW(firstcmd);
1461 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1466 /* If it didn't move the position, step to next command */
1467 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1469 /* Process any other parts of the command */
1471 BOOL processThese = executecmds;
1474 static const WCHAR ifElse[] = {'e','l','s','e'};
1476 /* execute all appropriate commands */
1477 curPosition = *cmdList;
1479 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1481 (*cmdList)->prevDelim,
1482 (*cmdList)->bracketDepth, myDepth);
1484 /* Execute any statements appended to the line */
1485 /* FIXME: Only if previous call worked for && or failed for || */
1486 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1487 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1488 if (processThese && (*cmdList)->command) {
1489 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1492 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1494 /* Execute any appended to the statement with (...) */
1495 } else if ((*cmdList)->bracketDepth > myDepth) {
1497 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1498 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1500 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1502 /* End of the command - does 'ELSE ' follow as the next command? */
1505 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1506 (*cmdList)->command)) {
1508 /* Swap between if and else processing */
1509 processThese = !processThese;
1511 /* Process the ELSE part */
1513 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1514 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1516 /* Skip leading whitespace between condition and the command */
1517 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1519 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1522 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1524 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1533 /*****************************************************************************
1534 * WCMD_parse_forf_options
1536 * Parses the for /f 'options', extracting the values and validating the
1537 * keywords. Note all keywords are optional.
1539 * options [I] The unparsed parameter string
1540 * eol [O] Set to the comment character (eol=x)
1541 * skip [O] Set to the number of lines to skip (skip=xx)
1542 * delims [O] Set to the token delimiters (delims=)
1543 * tokens [O] Set to the requested tokens, as provided (tokens=)
1544 * usebackq [O] Set to TRUE if usebackq found
1546 * Returns TRUE on success, FALSE on syntax error
1549 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1550 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1553 WCHAR *pos = options;
1554 int len = strlenW(pos);
1555 static const WCHAR eolW[] = {'e','o','l','='};
1556 static const WCHAR skipW[] = {'s','k','i','p','='};
1557 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1558 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1559 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1560 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1561 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1563 /* Initialize to defaults */
1564 strcpyW(delims, forf_defaultdelims);
1565 strcpyW(tokens, forf_defaulttokens);
1570 /* Strip (optional) leading and trailing quotes */
1571 if ((*pos == '"') && (pos[len-1] == '"')) {
1576 /* Process each keyword */
1577 while (pos && *pos) {
1578 if (*pos == ' ' || *pos == '\t') {
1581 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1582 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1583 pos, sizeof(eolW)/sizeof(WCHAR),
1584 eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1585 *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1586 pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1587 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1589 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1590 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1591 pos, sizeof(skipW)/sizeof(WCHAR),
1592 skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1593 WCHAR *nextchar = NULL;
1594 pos = pos + sizeof(skipW)/sizeof(WCHAR);
1595 *skip = strtoulW(pos, &nextchar, 0);
1596 WINE_TRACE("Found skip as %d lines\n", *skip);
1599 /* Save if usebackq semantics are in effect */
1600 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1601 pos, sizeof(usebackqW)/sizeof(WCHAR),
1602 usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1604 pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1605 WINE_TRACE("Found usebackq\n");
1607 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1608 if you finish the optionsroot string with delims= otherwise the space is
1609 just a token delimiter! */
1610 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1611 pos, sizeof(delimsW)/sizeof(WCHAR),
1612 delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1615 pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1616 while (*pos && *pos != ' ') {
1620 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1621 delims[i++] = 0; /* Null terminate the delims */
1622 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1624 /* Save the tokens being requested */
1625 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1626 pos, sizeof(tokensW)/sizeof(WCHAR),
1627 tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1630 pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1631 while (*pos && *pos != ' ') {
1635 tokens[i++] = 0; /* Null terminate the tokens */
1636 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1639 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1646 /*****************************************************************************
1647 * WCMD_add_dirstowalk
1649 * When recursing through directories (for /r), we need to add to the list of
1650 * directories still to walk, any subdirectories of the one we are processing.
1653 * options [I] The remaining list of directories still to process
1655 * Note this routine inserts the subdirectories found between the entry being
1656 * processed, and any other directory still to be processed, mimicing what
1659 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1660 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1661 WCHAR fullitem[MAX_PATH];
1662 WIN32_FIND_DATAW fd;
1665 /* Build a generic search and add all directories on the list of directories
1667 strcpyW(fullitem, dirsToWalk->dirName);
1668 strcatW(fullitem, slashstarW);
1669 hff = FindFirstFileW(fullitem, &fd);
1670 if (hff != INVALID_HANDLE_VALUE) {
1672 WINE_TRACE("Looking for subdirectories\n");
1673 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1674 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1675 (strcmpW(fd.cFileName, dotW) != 0))
1677 /* Allocate memory, add to list */
1678 DIRECTORY_STACK *toWalk = heap_alloc(sizeof(DIRECTORY_STACK));
1679 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1680 toWalk->next = remainingDirs->next;
1681 remainingDirs->next = toWalk;
1682 remainingDirs = toWalk;
1683 toWalk->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1684 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1685 strcatW(toWalk->dirName, slashW);
1686 strcatW(toWalk->dirName, fd.cFileName);
1687 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1688 toWalk, toWalk->next);
1690 } while (FindNextFileW(hff, &fd) != 0);
1691 WINE_TRACE("Finished adding all subdirectories\n");
1696 /**************************************************************************
1697 * WCMD_for_nexttoken
1699 * Parse the token= line, identifying the next highest number not processed
1700 * so far. Count how many tokens are referred (including duplicates) and
1701 * optionally return that, plus optionally indicate if the tokens= line
1705 * lasttoken [I] - Identifies the token index of the last one
1706 * returned so far (-1 used for first loop)
1707 * tokenstr [I] - The specified tokens= line
1708 * firstCmd [O] - Optionally indicate how many tokens are listed
1709 * doAll [O] - Optionally indicate if line ends with *
1710 * duplicates [O] - Optionally indicate if there is any evidence of
1711 * overlaying tokens in the string
1712 * Note the caller should keep a running track of duplicates as the tokens
1713 * are recursively passed. If any have duplicates, then the * token should
1716 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1717 int *totalfound, BOOL *doall,
1720 WCHAR *pos = tokenstr;
1723 if (totalfound) *totalfound = 0;
1724 if (doall) *doall = FALSE;
1725 if (duplicates) *duplicates = FALSE;
1727 WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken,
1728 wine_dbgstr_w(tokenstr), nexttoken);
1730 /* Loop through the token string, parsing it. Valid syntax is:
1731 token=m or x-y with comma delimiter and optionally * to finish*/
1733 int nextnumber1, nextnumber2 = -1;
1736 /* Get the next number */
1737 nextnumber1 = strtoulW(pos, &nextchar, 10);
1739 /* If it is followed by a minus, its a range, so get the next one as well */
1740 if (*nextchar == '-') {
1741 nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1743 /* We want to return the lowest number that is higher than lasttoken
1744 but only if range is positive */
1745 if (nextnumber2 >= nextnumber1 &&
1746 lasttoken < nextnumber2) {
1749 if (nexttoken == -1) {
1750 nextvalue = max(nextnumber1, (lasttoken+1));
1752 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1755 /* Flag if duplicates identified */
1756 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1758 nexttoken = nextvalue;
1761 /* Update the running total for the whole range */
1762 if (nextnumber2 >= nextnumber1 && totalfound) {
1763 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1767 if (totalfound) (*totalfound)++;
1769 /* See if the number found is one we have already seen */
1770 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1772 /* We want to return the lowest number that is higher than lasttoken */
1773 if (lasttoken < nextnumber1 &&
1774 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1775 nexttoken = nextnumber1;
1780 /* Remember if it is followed by a star, and if it is indicate a need to
1781 show all tokens, unless a duplicate has been found */
1782 if (*nextchar == '*') {
1783 if (doall) *doall = TRUE;
1784 if (totalfound) (*totalfound)++;
1787 /* Step on to the next character */
1793 if (nexttoken == -1) nexttoken = lasttoken;
1794 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1795 if (totalfound) WINE_TRACE("Found total tokens in total %d\n", *totalfound);
1796 if (doall && *doall) WINE_TRACE("Request for all tokens found\n");
1797 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1801 /**************************************************************************
1804 * When parsing file or string contents (for /f), once the string to parse
1805 * has been identified, handle the various options and call the do part
1809 * cmdStart [I] - Identifies the list of commands making up the
1810 * for loop body (especially if brackets in use)
1811 * firstCmd [I] - The textual start of the command after the DO
1812 * which is within the first item of cmdStart
1813 * cmdEnd [O] - Identifies where to continue after the DO
1814 * variable [I] - The variable identified on the for line
1815 * buffer [I] - The string to parse
1816 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1817 * forf_skip [I/O] - How many lines to skip first
1818 * forf_eol [I] - The 'end of line' (comment) character
1819 * forf_delims [I] - The delimiters to use when breaking the string apart
1820 * forf_tokens [I] - The tokens to use when breaking the string apart
1822 static void WCMD_parse_line(CMD_LIST *cmdStart,
1823 const WCHAR *firstCmd,
1825 const WCHAR variable,
1831 WCHAR *forf_tokens) {
1834 FOR_CONTEXT oldcontext;
1835 int varidx, varoffset;
1836 int nexttoken, lasttoken = -1;
1837 BOOL starfound = FALSE;
1838 BOOL thisduplicate = FALSE;
1839 BOOL anyduplicates = FALSE;
1842 /* Skip lines if requested */
1848 /* Save away any existing for variable context (e.g. nested for loops) */
1849 oldcontext = forloopcontext;
1851 /* Extract the parameters based on the tokens= value (There will always
1852 be some value, as if it is not supplied, it defaults to tokens=1).
1854 Count how many tokens are named in the line, identify the lowest
1855 Empty (set to null terminated string) that number of named variables
1856 While lasttoken != nextlowest
1857 %letter = parameter number 'nextlowest'
1858 letter++ (if >26 or >52 abort)
1859 Go through token= string finding next lowest number
1860 If token ends in * set %letter = raw position of token(nextnumber+1)
1863 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1864 NULL, &thisduplicate);
1865 varidx = FOR_VAR_IDX(variable);
1867 /* Empty out variables */
1869 varidx >= 0 && varoffset<totalfound && ((varidx+varoffset)%26);
1871 forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
1872 /* Stop if we walk beyond z or Z */
1873 if (((varidx+varoffset) % 26) == 0) break;
1876 /* Loop extracting the tokens */
1878 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1879 while (varidx >= 0 && (nexttoken > lasttoken)) {
1880 anyduplicates |= thisduplicate;
1882 /* Extract the token number requested and set into the next variable context */
1883 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, FALSE, FALSE, forf_delims);
1884 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1885 varidx + varoffset, wine_dbgstr_w(parm));
1887 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1889 if (((varidx + varoffset) %26) == 0) break;
1892 /* Find the next token */
1893 lasttoken = nexttoken;
1894 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1895 &starfound, &thisduplicate);
1898 /* If all the rest of the tokens were requested, and there is still space in
1899 the variable range, write them now */
1900 if (!anyduplicates && starfound && varidx >= 0 && ((varidx+varoffset) % 26)) {
1902 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
1903 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
1904 varidx + varoffset, wine_dbgstr_w(parm));
1905 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1908 /* Execute the body of the foor loop with these values */
1909 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
1910 CMD_LIST *thisCmdStart = cmdStart;
1912 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
1913 *cmdEnd = thisCmdStart;
1916 /* Free the duplicated strings, and restore the context */
1919 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
1920 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
1921 (forloopcontext.variable[i] != nullW)) {
1922 heap_free(forloopcontext.variable[i]);
1927 /* Restore the original for variable contextx */
1928 forloopcontext = oldcontext;
1931 /**************************************************************************
1932 * WCMD_forf_getinputhandle
1934 * Return a file handle which can be used for reading the input lines,
1935 * either to a specific file (which may be quote delimited as we have to
1936 * read the parameters in raw mode) or to a command which we need to
1937 * execute. The command being executed runs in its own shell and stores
1938 * its data in a temporary file.
1941 * usebackq [I] - Indicates whether usebackq is in effect or not
1942 * itemStr [I] - The item to be handled, either a filename or
1943 * whole command string to execute
1944 * iscmd [I] - Identifies whether this is a command or not
1946 * Returns a file handle which can be used to read the input lines from.
1948 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
1949 WCHAR temp_str[MAX_PATH];
1950 WCHAR temp_file[MAX_PATH];
1951 WCHAR temp_cmd[MAXSTRING];
1952 HANDLE hinput = INVALID_HANDLE_VALUE;
1953 static const WCHAR redirOutW[] = {'>','%','s','\0'};
1954 static const WCHAR cmdW[] = {'C','M','D','\0'};
1955 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
1956 '/','C',' ','"','%','s','"','\0'};
1958 /* Remove leading and trailing character */
1959 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
1960 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
1961 (!iscmd && (itemstr[0] == '"' && usebackq)))
1963 itemstr[strlenW(itemstr)-1] = 0x00;
1968 /* Get temp filename */
1969 GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
1970 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
1972 /* Redirect output to the temporary file */
1973 wsprintfW(temp_str, redirOutW, temp_file);
1974 wsprintfW(temp_cmd, cmdslashcW, itemstr);
1975 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
1976 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
1977 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
1979 /* Open the file, read line by line and process */
1980 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1981 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
1984 /* Open the file, read line by line and process */
1985 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
1986 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
1987 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1992 /**************************************************************************
1995 * Batch file loop processing.
1997 * On entry: cmdList contains the syntax up to the set
1998 * next cmdList and all in that bracket contain the set data
1999 * next cmdlist contains the DO cmd
2000 * following that is either brackets or && entries (as per if)
2004 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2006 WIN32_FIND_DATAW fd;
2009 static const WCHAR inW[] = {'i','n'};
2010 static const WCHAR doW[] = {'d','o'};
2011 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2014 WCHAR *oldvariablevalue;
2017 WCHAR optionsRoot[MAX_PATH];
2018 DIRECTORY_STACK *dirsToWalk = NULL;
2019 BOOL expandDirs = FALSE;
2020 BOOL useNumbers = FALSE;
2021 BOOL doFileset = FALSE;
2022 BOOL doRecurse = FALSE;
2023 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2024 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2026 CMD_LIST *thisCmdStart;
2027 int parameterNo = 0;
2030 WCHAR forf_delims[256];
2031 WCHAR forf_tokens[MAXSTRING];
2032 BOOL forf_usebackq = FALSE;
2034 /* Handle optional qualifiers (multiple are allowed) */
2035 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2038 while (thisArg && *thisArg == '/') {
2039 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2041 switch (toupperW(*thisArg)) {
2042 case 'D': expandDirs = TRUE; break;
2043 case 'L': useNumbers = TRUE; break;
2045 /* Recursive is special case - /R can have an optional path following it */
2046 /* filenamesets are another special case - /F can have an optional options following it */
2050 /* When recursing directories, use current directory as the starting point unless
2051 subsequently overridden */
2052 doRecurse = (toupperW(*thisArg) == 'R');
2053 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
2055 doFileset = (toupperW(*thisArg) == 'F');
2057 /* Retrieve next parameter to see if is root/options (raw form required
2058 with for /f, or unquoted in for /r) */
2059 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2061 /* Next parm is either qualifier, path/options or variable -
2062 only care about it if it is the path/options */
2063 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2065 strcpyW(optionsRoot, thisArg);
2070 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2073 /* Step to next token */
2074 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2077 /* Ensure line continues with variable */
2078 if (!*thisArg || *thisArg != '%') {
2079 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2083 /* With for /f parse the options if provided */
2085 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2086 forf_delims, forf_tokens, &forf_usebackq))
2088 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2092 /* Set up the list of directories to recurse if we are going to */
2093 } else if (doRecurse) {
2094 /* Allocate memory, add to list */
2095 dirsToWalk = heap_alloc(sizeof(DIRECTORY_STACK));
2096 dirsToWalk->next = NULL;
2097 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2098 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2101 /* Variable should follow */
2102 strcpyW(variable, thisArg);
2103 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2104 varidx = FOR_VAR_IDX(variable[1]);
2106 /* Ensure line continues with IN */
2107 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2109 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2110 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
2111 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
2112 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2116 /* Save away where the set of data starts and the variable */
2117 thisDepth = (*cmdList)->bracketDepth;
2118 *cmdList = (*cmdList)->nextcommand;
2119 setStart = (*cmdList);
2121 /* Skip until the close bracket */
2122 WINE_TRACE("Searching %p as the set\n", *cmdList);
2124 (*cmdList)->command != NULL &&
2125 (*cmdList)->bracketDepth > thisDepth) {
2126 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2127 *cmdList = (*cmdList)->nextcommand;
2130 /* Skip the close bracket, if there is one */
2131 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2133 /* Syntax error if missing close bracket, or nothing following it
2134 and once we have the complete set, we expect a DO */
2135 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2136 if ((*cmdList == NULL)
2137 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
2139 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2145 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2146 mode, or once for the rest of the time. */
2149 /* Save away the starting position for the commands (and offset for the
2151 cmdStart = *cmdList;
2152 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2155 /* If we are recursing directories (ie /R), add all sub directories now, then
2156 prefix the root when searching for the item */
2157 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2160 /* Loop through all set entries */
2162 thisSet->command != NULL &&
2163 thisSet->bracketDepth >= thisDepth) {
2165 /* Loop through all entries on the same line */
2168 WCHAR buffer[MAXSTRING];
2170 WINE_TRACE("Processing for set %p\n", thisSet);
2172 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2175 * If the parameter within the set has a wildcard then search for matching files
2176 * otherwise do a literal substitution.
2178 static const WCHAR wildcards[] = {'*','?','\0'};
2179 thisCmdStart = cmdStart;
2182 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2184 if (!useNumbers && !doFileset) {
2185 WCHAR fullitem[MAX_PATH];
2187 /* Now build the item to use / search for in the specified directory,
2188 as it is fully qualified in the /R case */
2190 strcpyW(fullitem, dirsToWalk->dirName);
2191 strcatW(fullitem, slashW);
2192 strcatW(fullitem, item);
2194 strcpyW(fullitem, item);
2197 if (strpbrkW (fullitem, wildcards)) {
2199 hff = FindFirstFileW(fullitem, &fd);
2200 if (hff != INVALID_HANDLE_VALUE) {
2202 BOOL isDirectory = FALSE;
2204 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2206 /* Handle as files or dirs appropriately, but ignore . and .. */
2207 if (isDirectory == expandDirs &&
2208 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2209 (strcmpW(fd.cFileName, dotW) != 0))
2211 thisCmdStart = cmdStart;
2212 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2215 strcpyW(fullitem, dirsToWalk->dirName);
2216 strcatW(fullitem, slashW);
2217 strcatW(fullitem, fd.cFileName);
2219 strcpyW(fullitem, fd.cFileName);
2223 /* Save away any existing for variable context (e.g. nested for loops)
2224 and restore it after executing the body of this for loop */
2226 oldvariablevalue = forloopcontext.variable[varidx];
2227 forloopcontext.variable[varidx] = fullitem;
2229 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2230 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2232 cmdEnd = thisCmdStart;
2234 } while (FindNextFileW(hff, &fd) != 0);
2240 /* Save away any existing for variable context (e.g. nested for loops)
2241 and restore it after executing the body of this for loop */
2243 oldvariablevalue = forloopcontext.variable[varidx];
2244 forloopcontext.variable[varidx] = fullitem;
2246 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2247 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2249 cmdEnd = thisCmdStart;
2252 } else if (useNumbers) {
2253 /* Convert the first 3 numbers to signed longs and save */
2254 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2255 /* else ignore them! */
2257 /* Filesets - either a list of files, or a command to run and parse the output */
2258 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2259 (forf_usebackq && *itemStart != '\''))) {
2264 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2265 wine_dbgstr_w(item));
2267 /* If backquote or single quote, we need to launch that command
2268 and parse the results - use a temporary file */
2269 if ((forf_usebackq && *itemStart == '`') ||
2270 (!forf_usebackq && *itemStart == '\'')) {
2272 /* Use itemstart because the command is the whole set, not just the first token */
2273 itemparm = itemStart;
2276 /* Use item because the file to process is just the first item in the set */
2279 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2281 /* Process the input file */
2282 if (input == INVALID_HANDLE_VALUE) {
2283 WCMD_print_error ();
2284 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2286 return; /* FOR loop aborts at first failure here */
2290 /* Read line by line until end of file */
2291 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2292 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2293 &forf_skip, forf_eol, forf_delims, forf_tokens);
2296 CloseHandle (input);
2299 /* When we have processed the item as a whole command, abort future set processing */
2300 if (itemparm==itemStart) {
2305 /* Filesets - A string literal */
2306 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2307 (forf_usebackq && *itemStart == '\''))) {
2309 /* Remove leading and trailing character, ready to parse with delims= delimiters
2310 Note that the last quote is removed from the set and the string terminates
2311 there to mimic windows */
2312 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2318 /* Copy the item away from the global buffer used by WCMD_parameter */
2319 strcpyW(buffer, itemStart);
2320 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2321 &forf_skip, forf_eol, forf_delims, forf_tokens);
2323 /* Only one string can be supplied in the whole set, abort future set processing */
2328 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2332 /* Move onto the next set line */
2333 if (thisSet) thisSet = thisSet->nextcommand;
2336 /* If /L is provided, now run the for loop */
2339 static const WCHAR fmt[] = {'%','d','\0'};
2341 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2342 numbers[0], numbers[2], numbers[1]);
2344 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2347 sprintfW(thisNum, fmt, i);
2348 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2350 thisCmdStart = cmdStart;
2353 /* Save away any existing for variable context (e.g. nested for loops)
2354 and restore it after executing the body of this for loop */
2356 oldvariablevalue = forloopcontext.variable[varidx];
2357 forloopcontext.variable[varidx] = thisNum;
2359 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2360 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2362 cmdEnd = thisCmdStart;
2365 /* If we are walking directories, move on to any which remain */
2366 if (dirsToWalk != NULL) {
2367 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2368 heap_free(dirsToWalk->dirName);
2369 heap_free(dirsToWalk);
2370 dirsToWalk = nextDir;
2371 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2372 wine_dbgstr_w(dirsToWalk->dirName));
2373 else WINE_TRACE("Finished all directories.\n");
2376 } while (dirsToWalk != NULL);
2378 /* Now skip over the do part if we did not perform the for loop so far.
2379 We store in cmdEnd the next command after the do block, but we only
2380 know this if something was run. If it has not been, we need to calculate
2383 thisCmdStart = cmdStart;
2384 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2385 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2386 cmdEnd = thisCmdStart;
2389 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2390 all processing, OR it should be pointing to the end of && processing OR
2391 it should be pointing at the NULL end of bracket for the DO. The return
2392 value needs to be the NEXT command to execute, which it either is, or
2393 we need to step over the closing bracket */
2395 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2398 /**************************************************************************
2401 * Simple on-line help. Help text is stored in the resource file.
2404 void WCMD_give_help (const WCHAR *args)
2408 args = WCMD_skip_leading_spaces((WCHAR*) args);
2409 if (strlenW(args) == 0) {
2410 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2413 /* Display help message for builtin commands */
2414 for (i=0; i<=WCMD_EXIT; i++) {
2415 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2416 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2417 WCMD_output_asis (WCMD_LoadMessage(i));
2421 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2422 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2423 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2424 args, -1, externals[i], -1) == CSTR_EQUAL) {
2426 static const WCHAR helpW[] = {' ', '/','?','\0'};
2428 strcatW(cmd, helpW);
2429 WCMD_run_program(cmd, FALSE);
2433 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2438 /****************************************************************************
2441 * Batch file jump instruction. Not the most efficient algorithm ;-)
2442 * Prints error message if the specified label cannot be found - the file pointer is
2443 * then at EOF, effectively stopping the batch file.
2444 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2447 void WCMD_goto (CMD_LIST **cmdList) {
2449 WCHAR string[MAX_PATH];
2450 WCHAR current[MAX_PATH];
2452 /* Do not process any more parts of a processed multipart or multilines command */
2453 if (cmdList) *cmdList = NULL;
2455 if (context != NULL) {
2456 WCHAR *paramStart = param1, *str;
2457 static const WCHAR eofW[] = {':','e','o','f','\0'};
2459 if (param1[0] == 0x00) {
2460 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2464 /* Handle special :EOF label */
2465 if (lstrcmpiW (eofW, param1) == 0) {
2466 context -> skip_rest = TRUE;
2470 /* Support goto :label as well as goto label */
2471 if (*paramStart == ':') paramStart++;
2473 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2474 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2476 while (isspaceW (*str)) str++;
2480 while (((current[index] = str[index])) && (!isspaceW (current[index])))
2483 /* ignore space at the end */
2485 if (lstrcmpiW (current, paramStart) == 0) return;
2488 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2493 /*****************************************************************************
2496 * Push a directory onto the stack
2499 void WCMD_pushd (const WCHAR *args)
2501 struct env_stack *curdir;
2503 static const WCHAR parmD[] = {'/','D','\0'};
2505 if (strchrW(args, '/') != NULL) {
2506 SetLastError(ERROR_INVALID_PARAMETER);
2511 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2512 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2513 if( !curdir || !thisdir ) {
2516 WINE_ERR ("out of memory\n");
2520 /* Change directory using CD code with /D parameter */
2521 strcpyW(quals, parmD);
2522 GetCurrentDirectoryW (1024, thisdir);
2524 WCMD_setshow_default(args);
2530 curdir -> next = pushd_directories;
2531 curdir -> strings = thisdir;
2532 if (pushd_directories == NULL) {
2533 curdir -> u.stackdepth = 1;
2535 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2537 pushd_directories = curdir;
2542 /*****************************************************************************
2545 * Pop a directory from the stack
2548 void WCMD_popd (void) {
2549 struct env_stack *temp = pushd_directories;
2551 if (!pushd_directories)
2554 /* pop the old environment from the stack, and make it the current dir */
2555 pushd_directories = temp->next;
2556 SetCurrentDirectoryW(temp->strings);
2557 LocalFree (temp->strings);
2561 /*******************************************************************
2562 * evaluate_if_comparison
2564 * Evaluates an "if" comparison operation
2567 * leftOperand [I] left operand, non NULL
2568 * operator [I] "if" binary comparison operator, non NULL
2569 * rightOperand [I] right operand, non NULL
2570 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2573 * Success: 1 if operator applied to the operands evaluates to TRUE
2574 * 0 if operator applied to the operands evaluates to FALSE
2575 * Failure: -1 if operator is not recognized
2577 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2578 const WCHAR *rightOperand, int caseInsensitive)
2580 WCHAR *endptr_leftOp, *endptr_rightOp;
2581 long int leftOperand_int, rightOperand_int;
2583 static const WCHAR lssW[] = {'l','s','s','\0'};
2584 static const WCHAR leqW[] = {'l','e','q','\0'};
2585 static const WCHAR equW[] = {'e','q','u','\0'};
2586 static const WCHAR neqW[] = {'n','e','q','\0'};
2587 static const WCHAR geqW[] = {'g','e','q','\0'};
2588 static const WCHAR gtrW[] = {'g','t','r','\0'};
2590 /* == is a special case, as it always compares strings */
2591 if (!lstrcmpiW(operator, eqeqW))
2592 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2593 : lstrcmpW (leftOperand, rightOperand) == 0;
2595 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2596 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2597 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2598 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2600 /* Perform actual (integer or string) comparison */
2601 if (!lstrcmpiW(operator, lssW)) {
2603 return leftOperand_int < rightOperand_int;
2605 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2606 : lstrcmpW (leftOperand, rightOperand) < 0;
2609 if (!lstrcmpiW(operator, leqW)) {
2611 return leftOperand_int <= rightOperand_int;
2613 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2614 : lstrcmpW (leftOperand, rightOperand) <= 0;
2617 if (!lstrcmpiW(operator, equW)) {
2619 return leftOperand_int == rightOperand_int;
2621 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2622 : lstrcmpW (leftOperand, rightOperand) == 0;
2625 if (!lstrcmpiW(operator, neqW)) {
2627 return leftOperand_int != rightOperand_int;
2629 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2630 : lstrcmpW (leftOperand, rightOperand) != 0;
2633 if (!lstrcmpiW(operator, geqW)) {
2635 return leftOperand_int >= rightOperand_int;
2637 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2638 : lstrcmpW (leftOperand, rightOperand) >= 0;
2641 if (!lstrcmpiW(operator, gtrW)) {
2643 return leftOperand_int > rightOperand_int;
2645 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2646 : lstrcmpW (leftOperand, rightOperand) > 0;
2652 /****************************************************************************
2655 * Batch file conditional.
2657 * On entry, cmdlist will point to command containing the IF, and optionally
2658 * the first command to execute (if brackets not found)
2659 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2660 * If ('s were found, execute all within that bracket
2661 * Command may optionally be followed by an ELSE - need to skip instructions
2662 * in the else using the same logic
2664 * FIXME: Much more syntax checking needed!
2666 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2668 int negate; /* Negate condition */
2669 int test; /* Condition evaluation result */
2670 WCHAR condition[MAX_PATH], *command;
2671 static const WCHAR notW[] = {'n','o','t','\0'};
2672 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2673 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2674 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2675 static const WCHAR parmI[] = {'/','I','\0'};
2676 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2678 negate = !lstrcmpiW(param1,notW);
2679 strcpyW(condition, (negate ? param2 : param1));
2680 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2682 if (!lstrcmpiW (condition, errlvlW)) {
2683 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2685 long int param_int = strtolW(param, &endptr, 10);
2686 if (*endptr) goto syntax_err;
2687 test = ((long int)errorlevel >= param_int);
2688 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2690 else if (!lstrcmpiW (condition, existW)) {
2691 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2692 != INVALID_FILE_ATTRIBUTES);
2693 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2695 else if (!lstrcmpiW (condition, defdW)) {
2696 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2698 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2700 else { /* comparison operation */
2701 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2704 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, ¶mStart, TRUE, FALSE));
2708 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2709 p = paramStart + strlenW(leftOperand);
2710 while (*p == ' ' || *p == '\t')
2713 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2714 strcpyW(operator, eqeqW);
2716 strcpyW(operator, WCMD_parameter(p, 0, ¶mStart, FALSE, FALSE));
2717 if (!*operator) goto syntax_err;
2719 p += strlenW(operator);
2721 strcpyW(rightOperand, WCMD_parameter(p, 0, ¶mStart, TRUE, FALSE));
2725 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2729 p = paramStart + strlenW(rightOperand);
2730 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2733 /* Process rest of IF statement which is on the same line
2734 Note: This may process all or some of the cmdList (eg a GOTO) */
2735 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2739 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2742 /****************************************************************************
2745 * Move a file, directory tree or wildcarded set of files.
2748 void WCMD_move (void)
2751 WIN32_FIND_DATAW fd;
2753 WCHAR input[MAX_PATH];
2754 WCHAR output[MAX_PATH];
2756 WCHAR dir[MAX_PATH];
2757 WCHAR fname[MAX_PATH];
2758 WCHAR ext[MAX_PATH];
2760 if (param1[0] == 0x00) {
2761 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2765 /* If no destination supplied, assume current directory */
2766 if (param2[0] == 0x00) {
2767 strcpyW(param2, dotW);
2770 /* If 2nd parm is directory, then use original filename */
2771 /* Convert partial path to full path */
2772 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2773 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2774 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2775 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2777 /* Split into components */
2778 WCMD_splitpath(input, drive, dir, fname, ext);
2780 hff = FindFirstFileW(input, &fd);
2781 if (hff == INVALID_HANDLE_VALUE)
2785 WCHAR dest[MAX_PATH];
2786 WCHAR src[MAX_PATH];
2790 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2792 /* Build src & dest name */
2793 strcpyW(src, drive);
2796 /* See if dest is an existing directory */
2797 attribs = GetFileAttributesW(output);
2798 if (attribs != INVALID_FILE_ATTRIBUTES &&
2799 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2800 strcpyW(dest, output);
2801 strcatW(dest, slashW);
2802 strcatW(dest, fd.cFileName);
2804 strcpyW(dest, output);
2807 strcatW(src, fd.cFileName);
2809 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2810 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2812 /* If destination exists, prompt unless /Y supplied */
2813 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2815 WCHAR copycmd[MAXSTRING];
2818 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2819 if (strstrW (quals, parmNoY))
2821 else if (strstrW (quals, parmY))
2824 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2825 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2826 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2827 && ! lstrcmpiW (copycmd, parmY));
2830 /* Prompt if overwriting */
2834 /* Ask for confirmation */
2835 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2836 ok = WCMD_ask_confirm(question, FALSE, NULL);
2837 LocalFree(question);
2839 /* So delete the destination prior to the move */
2841 if (!DeleteFileW(dest)) {
2842 WCMD_print_error ();
2851 status = MoveFileW(src, dest);
2853 status = 1; /* Anything other than 0 to prevent error msg below */
2857 WCMD_print_error ();
2860 } while (FindNextFileW(hff, &fd) != 0);
2865 /****************************************************************************
2868 * Suspend execution of a batch script until a key is typed
2871 void WCMD_pause (void)
2877 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2879 have_console = GetConsoleMode(hIn, &oldmode);
2881 SetConsoleMode(hIn, 0);
2883 WCMD_output_asis(anykey);
2884 WCMD_ReadFile(hIn, &key, 1, &count);
2886 SetConsoleMode(hIn, oldmode);
2889 /****************************************************************************
2892 * Delete a directory.
2895 void WCMD_remove_dir (WCHAR *args) {
2898 int argsProcessed = 0;
2900 static const WCHAR parmS[] = {'/','S','\0'};
2901 static const WCHAR parmQ[] = {'/','Q','\0'};
2903 /* Loop through all args */
2905 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2906 if (argN && argN[0] != '/') {
2907 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2908 wine_dbgstr_w(quals));
2911 /* If subdirectory search not supplied, just try to remove
2912 and report error if it fails (eg if it contains a file) */
2913 if (strstrW (quals, parmS) == NULL) {
2914 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2916 /* Otherwise use ShFileOp to recursively remove a directory */
2919 SHFILEOPSTRUCTW lpDir;
2922 if (strstrW (quals, parmQ) == NULL) {
2924 WCHAR question[MAXSTRING];
2925 static const WCHAR fmt[] = {'%','s',' ','\0'};
2927 /* Ask for confirmation */
2928 wsprintfW(question, fmt, thisArg);
2929 ok = WCMD_ask_confirm(question, TRUE, NULL);
2931 /* Abort if answer is 'N' */
2938 lpDir.pFrom = thisArg;
2939 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2940 lpDir.wFunc = FO_DELETE;
2942 /* SHFileOperationW needs file list with a double null termination */
2943 thisArg[lstrlenW(thisArg) + 1] = 0x00;
2945 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
2950 /* Handle no valid args */
2951 if (argsProcessed == 0) {
2952 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2958 /****************************************************************************
2964 void WCMD_rename (void)
2968 WIN32_FIND_DATAW fd;
2969 WCHAR input[MAX_PATH];
2970 WCHAR *dotDst = NULL;
2972 WCHAR dir[MAX_PATH];
2973 WCHAR fname[MAX_PATH];
2974 WCHAR ext[MAX_PATH];
2978 /* Must be at least two args */
2979 if (param1[0] == 0x00 || param2[0] == 0x00) {
2980 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2985 /* Destination cannot contain a drive letter or directory separator */
2986 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
2987 SetLastError(ERROR_INVALID_PARAMETER);
2993 /* Convert partial path to full path */
2994 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2995 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2996 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
2997 dotDst = strchrW(param2, '.');
2999 /* Split into components */
3000 WCMD_splitpath(input, drive, dir, fname, ext);
3002 hff = FindFirstFileW(input, &fd);
3003 if (hff == INVALID_HANDLE_VALUE)
3007 WCHAR dest[MAX_PATH];
3008 WCHAR src[MAX_PATH];
3009 WCHAR *dotSrc = NULL;
3012 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3014 /* FIXME: If dest name or extension is *, replace with filename/ext
3015 part otherwise use supplied name. This supports:
3017 ren jim.* fred.* etc
3018 However, windows has a more complex algorithm supporting eg
3019 ?'s and *'s mid name */
3020 dotSrc = strchrW(fd.cFileName, '.');
3022 /* Build src & dest name */
3023 strcpyW(src, drive);
3026 dirLen = strlenW(src);
3027 strcatW(src, fd.cFileName);
3030 if (param2[0] == '*') {
3031 strcatW(dest, fd.cFileName);
3032 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3034 strcatW(dest, param2);
3035 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3038 /* Build Extension */
3039 if (dotDst && (*(dotDst+1)=='*')) {
3040 if (dotSrc) strcatW(dest, dotSrc);
3041 } else if (dotDst) {
3042 if (dotDst) strcatW(dest, dotDst);
3045 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3046 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3048 status = MoveFileW(src, dest);
3051 WCMD_print_error ();
3054 } while (FindNextFileW(hff, &fd) != 0);
3059 /*****************************************************************************
3062 * Make a copy of the environment.
3064 static WCHAR *WCMD_dupenv( const WCHAR *env )
3074 len += (strlenW(&env[len]) + 1);
3076 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3079 WINE_ERR("out of memory\n");
3082 memcpy (env_copy, env, len*sizeof (WCHAR));
3088 /*****************************************************************************
3091 * setlocal pushes the environment onto a stack
3092 * Save the environment as unicode so we don't screw anything up.
3094 void WCMD_setlocal (const WCHAR *s) {
3096 struct env_stack *env_copy;
3097 WCHAR cwd[MAX_PATH];
3099 /* setlocal does nothing outside of batch programs */
3100 if (!context) return;
3102 /* DISABLEEXTENSIONS ignored */
3104 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3107 WINE_ERR ("out of memory\n");
3111 env = GetEnvironmentStringsW ();
3112 env_copy->strings = WCMD_dupenv (env);
3113 if (env_copy->strings)
3115 env_copy->batchhandle = context->h;
3116 env_copy->next = saved_environment;
3117 saved_environment = env_copy;
3119 /* Save the current drive letter */
3120 GetCurrentDirectoryW(MAX_PATH, cwd);
3121 env_copy->u.cwd = cwd[0];
3124 LocalFree (env_copy);
3126 FreeEnvironmentStringsW (env);
3130 /*****************************************************************************
3133 * endlocal pops the environment off a stack
3134 * Note: When searching for '=', search from WCHAR position 1, to handle
3135 * special internal environment variables =C:, =D: etc
3137 void WCMD_endlocal (void) {
3138 WCHAR *env, *old, *p;
3139 struct env_stack *temp;
3142 /* setlocal does nothing outside of batch programs */
3143 if (!context) return;
3145 /* setlocal needs a saved environment from within the same context (batch
3146 program) as it was saved in */
3147 if (!saved_environment || saved_environment->batchhandle != context->h)
3150 /* pop the old environment from the stack */
3151 temp = saved_environment;
3152 saved_environment = temp->next;
3154 /* delete the current environment, totally */
3155 env = GetEnvironmentStringsW ();
3156 old = WCMD_dupenv (env);
3159 n = strlenW(&old[len]) + 1;
3160 p = strchrW(&old[len] + 1, '=');
3164 SetEnvironmentVariableW (&old[len], NULL);
3169 FreeEnvironmentStringsW (env);
3171 /* restore old environment */
3172 env = temp->strings;
3175 n = strlenW(&env[len]) + 1;
3176 p = strchrW(&env[len] + 1, '=');
3180 SetEnvironmentVariableW (&env[len], p);
3185 /* Restore current drive letter */
3186 if (IsCharAlphaW(temp->u.cwd)) {
3188 WCHAR cwd[MAX_PATH];
3189 static const WCHAR fmt[] = {'=','%','c',':','\0'};
3191 wsprintfW(envvar, fmt, temp->u.cwd);
3192 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3193 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3194 SetCurrentDirectoryW(cwd);
3202 /*****************************************************************************
3203 * WCMD_setshow_default
3205 * Set/Show the current default directory
3208 void WCMD_setshow_default (const WCHAR *args) {
3214 WIN32_FIND_DATAW fd;
3216 static const WCHAR parmD[] = {'/','D','\0'};
3218 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3220 /* Skip /D and trailing whitespace if on the front of the command line */
3221 if (CompareStringW(LOCALE_USER_DEFAULT,
3222 NORM_IGNORECASE | SORT_STRINGSORT,
3223 args, 2, parmD, -1) == CSTR_EQUAL) {
3225 while (*args && (*args==' ' || *args=='\t'))
3229 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3230 if (strlenW(args) == 0) {
3231 strcatW (cwd, newlineW);
3232 WCMD_output_asis (cwd);
3235 /* Remove any double quotes, which may be in the
3236 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3239 if (*args != '"') *pos++ = *args;
3242 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3246 /* Search for appropriate directory */
3247 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3248 hff = FindFirstFileW(string, &fd);
3249 if (hff != INVALID_HANDLE_VALUE) {
3251 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3252 WCHAR fpath[MAX_PATH];
3254 WCHAR dir[MAX_PATH];
3255 WCHAR fname[MAX_PATH];
3256 WCHAR ext[MAX_PATH];
3257 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3259 /* Convert path into actual directory spec */
3260 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3261 WCMD_splitpath(fpath, drive, dir, fname, ext);
3264 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3267 } while (FindNextFileW(hff, &fd) != 0);
3271 /* Change to that directory */
3272 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3274 status = SetCurrentDirectoryW(string);
3277 WCMD_print_error ();
3281 /* Save away the actual new directory, to store as current location */
3282 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3284 /* Restore old directory if drive letter would change, and
3285 CD x:\directory /D (or pushd c:\directory) not supplied */
3286 if ((strstrW(quals, parmD) == NULL) &&
3287 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3288 SetCurrentDirectoryW(cwd);
3292 /* Set special =C: type environment variable, for drive letter of
3293 change of directory, even if path was restored due to missing
3294 /D (allows changing drive letter when not resident on that
3296 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3298 strcpyW(env, equalW);
3299 memcpy(env+1, string, 2 * sizeof(WCHAR));
3301 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3302 SetEnvironmentVariableW(env, string);
3309 /****************************************************************************
3312 * Set/Show the system date
3313 * FIXME: Can't change date yet
3316 void WCMD_setshow_date (void) {
3318 WCHAR curdate[64], buffer[64];
3320 static const WCHAR parmT[] = {'/','T','\0'};
3322 if (strlenW(param1) == 0) {
3323 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3324 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3325 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3326 if (strstrW (quals, parmT) == NULL) {
3327 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3328 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3330 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3334 else WCMD_print_error ();
3337 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3341 /****************************************************************************
3343 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3346 static int WCMD_compare( const void *a, const void *b )
3349 const WCHAR * const *str_a = a, * const *str_b = b;
3350 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3351 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3352 if( r == CSTR_LESS_THAN ) return -1;
3353 if( r == CSTR_GREATER_THAN ) return 1;
3357 /****************************************************************************
3358 * WCMD_setshow_sortenv
3360 * sort variables into order for display
3361 * Optionally only display those who start with a stub
3362 * returns the count displayed
3364 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3366 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3369 if (stub) stublen = strlenW(stub);
3371 /* count the number of strings, and the total length */
3373 len += (strlenW(&s[len]) + 1);
3377 /* add the strings to an array */
3378 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3382 for( i=1; i<count; i++ )
3383 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3385 /* sort the array */
3386 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3389 for( i=0; i<count; i++ ) {
3390 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3391 NORM_IGNORECASE | SORT_STRINGSORT,
3392 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3393 /* Don't display special internal variables */
3394 if (str[i][0] != '=') {
3395 WCMD_output_asis(str[i]);
3396 WCMD_output_asis(newlineW);
3403 return displayedcount;
3406 /****************************************************************************
3409 * Set/Show the environment variables
3412 void WCMD_setshow_env (WCHAR *s) {
3417 static const WCHAR parmP[] = {'/','P','\0'};
3419 if (param1[0] == 0x00 && quals[0] == 0x00) {
3420 env = GetEnvironmentStringsW();
3421 WCMD_setshow_sortenv( env, NULL );
3425 /* See if /P supplied, and if so echo the prompt, and read in a reply */
3426 if (CompareStringW(LOCALE_USER_DEFAULT,
3427 NORM_IGNORECASE | SORT_STRINGSORT,
3428 s, 2, parmP, -1) == CSTR_EQUAL) {
3429 WCHAR string[MAXSTRING];
3433 while (*s && (*s==' ' || *s=='\t')) s++;
3435 WCMD_strip_quotes(s);
3437 /* If no parameter, or no '=' sign, return an error */
3438 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
3439 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3443 /* Output the prompt */
3445 if (strlenW(p) != 0) WCMD_output_asis(p);
3447 /* Read the reply */
3448 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3450 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3451 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3452 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3453 wine_dbgstr_w(string));
3454 status = SetEnvironmentVariableW(s, string);
3461 WCMD_strip_quotes(s);
3462 p = strchrW (s, '=');
3464 env = GetEnvironmentStringsW();
3465 if (WCMD_setshow_sortenv( env, s ) == 0) {
3466 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
3473 if (strlenW(p) == 0) p = NULL;
3474 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3476 status = SetEnvironmentVariableW(s, p);
3477 gle = GetLastError();
3478 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
3480 } else if ((!status)) WCMD_print_error();
3481 else errorlevel = 0;
3485 /****************************************************************************
3488 * Set/Show the path environment variable
3491 void WCMD_setshow_path (const WCHAR *args) {
3495 static const WCHAR pathW[] = {'P','A','T','H','\0'};
3496 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
3498 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
3499 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
3501 WCMD_output_asis ( pathEqW);
3502 WCMD_output_asis ( string);
3503 WCMD_output_asis ( newlineW);
3506 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
3510 if (*args == '=') args++; /* Skip leading '=' */
3511 status = SetEnvironmentVariableW(pathW, args);
3512 if (!status) WCMD_print_error();
3516 /****************************************************************************
3517 * WCMD_setshow_prompt
3519 * Set or show the command prompt.
3522 void WCMD_setshow_prompt (void) {
3525 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
3527 if (strlenW(param1) == 0) {
3528 SetEnvironmentVariableW(promptW, NULL);
3532 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
3533 if (strlenW(s) == 0) {
3534 SetEnvironmentVariableW(promptW, NULL);
3536 else SetEnvironmentVariableW(promptW, s);
3540 /****************************************************************************
3543 * Set/Show the system time
3544 * FIXME: Can't change time yet
3547 void WCMD_setshow_time (void) {
3549 WCHAR curtime[64], buffer[64];
3552 static const WCHAR parmT[] = {'/','T','\0'};
3554 if (strlenW(param1) == 0) {
3556 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
3557 curtime, sizeof(curtime)/sizeof(WCHAR))) {
3558 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
3559 if (strstrW (quals, parmT) == NULL) {
3560 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
3561 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3563 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3567 else WCMD_print_error ();
3570 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3574 /****************************************************************************
3577 * Shift batch parameters.
3578 * Optional /n says where to start shifting (n=0-8)
3581 void WCMD_shift (const WCHAR *args) {
3584 if (context != NULL) {
3585 WCHAR *pos = strchrW(args, '/');
3590 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
3591 start = (*(pos+1) - '0');
3593 SetLastError(ERROR_INVALID_PARAMETER);
3598 WINE_TRACE("Shifting variables, starting at %d\n", start);
3599 for (i=start;i<=8;i++) {
3600 context -> shift_count[i] = context -> shift_count[i+1] + 1;
3602 context -> shift_count[9] = context -> shift_count[9] + 1;
3607 /****************************************************************************
3610 void WCMD_start(const WCHAR *args)
3612 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
3613 '\\','s','t','a','r','t','.','e','x','e',0};
3614 WCHAR file[MAX_PATH];
3617 PROCESS_INFORMATION pi;
3619 GetWindowsDirectoryW( file, MAX_PATH );
3620 strcatW( file, exeW );
3621 cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
3622 strcpyW( cmdline, file );
3623 strcatW( cmdline, spaceW );
3624 strcatW( cmdline, args );
3626 memset( &st, 0, sizeof(STARTUPINFOW) );
3627 st.cb = sizeof(STARTUPINFOW);
3629 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
3631 WaitForSingleObject( pi.hProcess, INFINITE );
3632 GetExitCodeProcess( pi.hProcess, &errorlevel );
3633 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
3634 CloseHandle(pi.hProcess);
3635 CloseHandle(pi.hThread);
3639 SetLastError(ERROR_FILE_NOT_FOUND);
3640 WCMD_print_error ();
3646 /****************************************************************************
3649 * Set the console title
3651 void WCMD_title (const WCHAR *args) {
3652 SetConsoleTitleW(args);
3655 /****************************************************************************
3658 * Copy a file to standard output.
3661 void WCMD_type (WCHAR *args) {
3665 BOOL writeHeaders = FALSE;
3667 if (param1[0] == 0x00) {
3668 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3672 if (param2[0] != 0x00) writeHeaders = TRUE;
3674 /* Loop through all args */
3677 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3685 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3686 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3687 FILE_ATTRIBUTE_NORMAL, NULL);
3688 if (h == INVALID_HANDLE_VALUE) {
3689 WCMD_print_error ();
3690 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3694 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
3695 WCMD_output(fmt, thisArg);
3697 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
3698 if (count == 0) break; /* ReadFile reports success on EOF! */
3700 WCMD_output_asis (buffer);
3707 /****************************************************************************
3710 * Output either a file or stdin to screen in pages
3713 void WCMD_more (WCHAR *args) {
3718 WCHAR moreStrPage[100];
3721 static const WCHAR moreStart[] = {'-','-',' ','\0'};
3722 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
3723 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
3724 ')',' ','-','-','\n','\0'};
3725 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
3727 /* Prefix the NLS more with '-- ', then load the text */
3729 strcpyW(moreStr, moreStart);
3730 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
3731 (sizeof(moreStr)/sizeof(WCHAR))-3);
3733 if (param1[0] == 0x00) {
3735 /* Wine implements pipes via temporary files, and hence stdin is
3736 effectively reading from the file. This means the prompts for
3737 more are satisfied by the next line from the input (file). To
3738 avoid this, ensure stdin is to the console */
3739 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
3740 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
3741 FILE_SHARE_READ, NULL, OPEN_EXISTING,
3742 FILE_ATTRIBUTE_NORMAL, 0);
3743 WINE_TRACE("No parms - working probably in pipe mode\n");
3744 SetStdHandle(STD_INPUT_HANDLE, hConIn);
3746 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
3747 once you get in this bit unless due to a pipe, its going to end badly... */
3748 wsprintfW(moreStrPage, moreFmt, moreStr);
3750 WCMD_enter_paged_mode(moreStrPage);
3751 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3752 if (count == 0) break; /* ReadFile reports success on EOF! */
3754 WCMD_output_asis (buffer);
3756 WCMD_leave_paged_mode();
3758 /* Restore stdin to what it was */
3759 SetStdHandle(STD_INPUT_HANDLE, hstdin);
3760 CloseHandle(hConIn);
3764 BOOL needsPause = FALSE;
3766 /* Loop through all args */
3767 WINE_TRACE("Parms supplied - working through each file\n");
3768 WCMD_enter_paged_mode(moreStrPage);
3771 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3779 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
3780 WCMD_leave_paged_mode();
3781 WCMD_output_asis(moreStrPage);
3782 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3783 WCMD_enter_paged_mode(moreStrPage);
3787 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3788 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3789 FILE_ATTRIBUTE_NORMAL, NULL);
3790 if (h == INVALID_HANDLE_VALUE) {
3791 WCMD_print_error ();
3792 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3796 ULONG64 fileLen = 0;
3797 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
3799 /* Get the file size */
3800 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
3801 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
3804 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3805 if (count == 0) break; /* ReadFile reports success on EOF! */
3809 /* Update % count (would be used in WCMD_output_asis as prompt) */
3810 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
3812 WCMD_output_asis (buffer);
3818 WCMD_leave_paged_mode();
3822 /****************************************************************************
3825 * Display verify flag.
3826 * FIXME: We don't actually do anything with the verify flag other than toggle
3830 void WCMD_verify (const WCHAR *args) {
3834 count = strlenW(args);
3836 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
3837 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
3840 if (lstrcmpiW(args, onW) == 0) {
3844 else if (lstrcmpiW(args, offW) == 0) {
3845 verify_mode = FALSE;
3848 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
3851 /****************************************************************************
3854 * Display version info.
3857 void WCMD_version (void) {
3859 WCMD_output_asis (version_string);
3863 /****************************************************************************
3866 * Display volume information (set_label = FALSE)
3867 * Additionally set volume label (set_label = TRUE)
3868 * Returns 1 on success, 0 otherwise
3871 int WCMD_volume(BOOL set_label, const WCHAR *path)
3873 DWORD count, serial;
3874 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
3877 if (strlenW(path) == 0) {
3878 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
3880 WCMD_print_error ();
3883 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
3884 &serial, NULL, NULL, NULL, 0);
3887 static const WCHAR fmt[] = {'%','s','\\','\0'};
3888 if ((path[1] != ':') || (strlenW(path) != 2)) {
3889 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
3892 wsprintfW (curdir, fmt, path);
3893 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
3898 WCMD_print_error ();
3901 if (label[0] != '\0') {
3902 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
3906 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
3909 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
3910 HIWORD(serial), LOWORD(serial));
3912 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
3913 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3915 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3916 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3918 if (strlenW(path) != 0) {
3919 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
3922 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
3928 /**************************************************************************
3931 * Exit either the process, or just this batch program
3935 void WCMD_exit (CMD_LIST **cmdList) {
3937 static const WCHAR parmB[] = {'/','B','\0'};
3938 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
3940 if (context && lstrcmpiW(quals, parmB) == 0) {
3942 context -> skip_rest = TRUE;
3950 /*****************************************************************************
3953 * Lists or sets file associations (assoc = TRUE)
3954 * Lists or sets file types (assoc = FALSE)
3956 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
3959 DWORD accessOptions = KEY_READ;
3961 LONG rc = ERROR_SUCCESS;
3962 WCHAR keyValue[MAXSTRING];
3963 DWORD valueLen = MAXSTRING;
3965 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
3966 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
3968 /* See if parameter includes '=' */
3970 newValue = strchrW(args, '=');
3971 if (newValue) accessOptions |= KEY_WRITE;
3973 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
3974 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
3975 accessOptions, &key) != ERROR_SUCCESS) {
3976 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
3980 /* If no parameters then list all associations */
3981 if (*args == 0x00) {
3984 /* Enumerate all the keys */
3985 while (rc != ERROR_NO_MORE_ITEMS) {
3986 WCHAR keyName[MAXSTRING];
3989 /* Find the next value */
3990 nameLen = MAXSTRING;
3991 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
3993 if (rc == ERROR_SUCCESS) {
3995 /* Only interested in extension ones if assoc, or others
3997 if ((keyName[0] == '.' && assoc) ||
3998 (!(keyName[0] == '.') && (!assoc)))
4000 WCHAR subkey[MAXSTRING];
4001 strcpyW(subkey, keyName);
4002 if (!assoc) strcatW(subkey, shOpCmdW);
4004 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4006 valueLen = sizeof(keyValue)/sizeof(WCHAR);
4007 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4008 WCMD_output_asis(keyName);
4009 WCMD_output_asis(equalW);
4010 /* If no default value found, leave line empty after '=' */
4011 if (rc == ERROR_SUCCESS) {
4012 WCMD_output_asis(keyValue);
4014 WCMD_output_asis(newlineW);
4015 RegCloseKey(readKey);
4023 /* Parameter supplied - if no '=' on command line, its a query */
4024 if (newValue == NULL) {
4026 WCHAR subkey[MAXSTRING];
4028 /* Query terminates the parameter at the first space */
4029 strcpyW(keyValue, args);
4030 space = strchrW(keyValue, ' ');
4031 if (space) *space=0x00;
4033 /* Set up key name */
4034 strcpyW(subkey, keyValue);
4035 if (!assoc) strcatW(subkey, shOpCmdW);
4037 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4039 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4040 WCMD_output_asis(args);
4041 WCMD_output_asis(equalW);
4042 /* If no default value found, leave line empty after '=' */
4043 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4044 WCMD_output_asis(newlineW);
4045 RegCloseKey(readKey);
4048 WCHAR msgbuffer[MAXSTRING];
4050 /* Load the translated 'File association not found' */
4052 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4054 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4056 WCMD_output_stderr(msgbuffer, keyValue);
4060 /* Not a query - its a set or clear of a value */
4063 WCHAR subkey[MAXSTRING];
4065 /* Get pointer to new value */
4069 /* Set up key name */
4070 strcpyW(subkey, args);
4071 if (!assoc) strcatW(subkey, shOpCmdW);
4073 /* If nothing after '=' then clear value - only valid for ASSOC */
4074 if (*newValue == 0x00) {
4076 if (assoc) rc = RegDeleteKeyW(key, args);
4077 if (assoc && rc == ERROR_SUCCESS) {
4078 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4080 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4085 WCHAR msgbuffer[MAXSTRING];
4087 /* Load the translated 'File association not found' */
4089 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
4090 sizeof(msgbuffer)/sizeof(WCHAR));
4092 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
4093 sizeof(msgbuffer)/sizeof(WCHAR));
4095 WCMD_output_stderr(msgbuffer, keyValue);
4099 /* It really is a set value = contents */
4101 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4102 accessOptions, NULL, &readKey, NULL);
4103 if (rc == ERROR_SUCCESS) {
4104 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4106 sizeof(WCHAR) * (strlenW(newValue) + 1));
4107 RegCloseKey(readKey);
4110 if (rc != ERROR_SUCCESS) {
4114 WCMD_output_asis(args);
4115 WCMD_output_asis(equalW);
4116 WCMD_output_asis(newValue);
4117 WCMD_output_asis(newlineW);
4127 /****************************************************************************
4130 * Colors the terminal screen.
4133 void WCMD_color (void) {
4135 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4136 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4138 if (param1[0] != 0x00 && strlenW(param1) > 2) {
4139 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4143 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4149 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4154 /* Convert the color hex digits */
4155 if (param1[0] == 0x00) {
4156 color = defaultColor;
4158 color = strtoulW(param1, NULL, 16);
4161 /* Fail if fg == bg color */
4162 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4167 /* Set the current screen contents and ensure all future writes
4168 remain this color */
4169 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4170 SetConsoleTextAttribute(hStdOut, color);