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 */
445 char buffer[MAXSTRING];
447 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
450 /* Stop at first EOF */
452 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
453 if (ptr) bytesread = (ptr - buffer);
457 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
458 if (!ok || byteswritten != bytesread) {
459 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
460 wine_dbgstr_w(dstname), GetLastError());
464 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
465 wine_dbgstr_w(srcname), GetLastError());
467 } while (ok && bytesread > 0);
474 /****************************************************************************
477 * Copy a file or wildcarded set.
478 * For ascii/binary type copies, it gets complex:
479 * Syntax on command line is
480 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
481 * Where first /a or /b sets 'mode in operation' until another is found
482 * once another is found, it applies to the file preceding the /a or /b
483 * In addition each filename can contain wildcards
484 * To make matters worse, the + may be in the same parameter (i.e. no
485 * whitespace) or with whitespace separating it
487 * ASCII mode on read == read and stop at first EOF
488 * ASCII mode on write == append EOF to destination
489 * Binary == copy as-is
491 * Design of this is to build up a list of files which will be copied into a
492 * list, then work through the list file by file.
493 * If no destination is specified, it defaults to the name of the first file in
494 * the list, but the current directory.
498 void WCMD_copy(WCHAR * args) {
500 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
505 HANDLE hff = INVALID_HANDLE_VALUE;
506 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
507 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
508 BOOL anyconcats = FALSE; /* Have we found any + options */
509 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
510 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
511 BOOL prompt; /* Prompt before overwriting */
512 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
513 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
517 BOOL dstisdevice = FALSE;
518 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
520 typedef struct _COPY_FILES
522 struct _COPY_FILES *next;
527 COPY_FILES *sourcelist = NULL;
528 COPY_FILES *lastcopyentry = NULL;
529 COPY_FILES *destination = NULL;
530 COPY_FILES *thiscopy = NULL;
531 COPY_FILES *prevcopy = NULL;
533 /* Assume we were successful! */
536 /* If no args supplied at all, report an error */
537 if (param1[0] == 0x00) {
538 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
543 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
545 /* Walk through all args, building up a list of files to process */
546 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
547 while (*(thisparam)) {
551 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
553 /* Handle switches */
554 if (*thisparam == '/') {
555 while (*thisparam == '/') {
557 if (toupperW(*thisparam) == 'D') {
559 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
560 } else if (toupperW(*thisparam) == 'Y') {
562 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
564 } else if (toupperW(*thisparam) == 'V') {
566 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
567 } else if (toupperW(*thisparam) == 'N') {
569 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
570 } else if (toupperW(*thisparam) == 'Z') {
572 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
573 } else if (toupperW(*thisparam) == 'A') {
574 if (binarymode != 0) {
576 WINE_TRACE("Subsequent files will be handled as ASCII\n");
577 if (destination != NULL) {
578 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
579 destination->binarycopy = binarymode;
580 } else if (lastcopyentry != NULL) {
581 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
582 lastcopyentry->binarycopy = binarymode;
585 } else if (toupperW(*thisparam) == 'B') {
586 if (binarymode != 1) {
588 WINE_TRACE("Subsequent files will be handled as binary\n");
589 if (destination != NULL) {
590 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
591 destination->binarycopy = binarymode;
592 } else if (lastcopyentry != NULL) {
593 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
594 lastcopyentry->binarycopy = binarymode;
598 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
603 /* This parameter was purely switches, get the next one */
604 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
608 /* We have found something which is not a switch. If could be anything of the form
609 sourcefilename (which could be destination too)
610 + (when filename + filename syntex used)
611 sourcefilename+sourcefilename
613 +/b[tests show windows then ignores to end of parameter]
616 if (*thisparam=='+') {
617 if (lastcopyentry == NULL) {
618 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
622 concatnextfilename = TRUE;
626 /* Move to next thing to process */
628 if (*thisparam == 0x00)
629 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
633 /* We have found something to process - build a COPY_FILE block to store it */
634 thiscopy = heap_alloc(sizeof(COPY_FILES));
636 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
637 thiscopy->concatenate = concatnextfilename;
638 thiscopy->binarycopy = binarymode;
639 thiscopy->next = NULL;
641 /* Time to work out the name. Allocate at least enough space (deliberately too much to
642 leave space to append \* to the end) , then copy in character by character. Strip off
643 quotes if we find them. */
644 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
645 thiscopy->name = heap_alloc(len*sizeof(WCHAR));
646 memset(thiscopy->name, 0x00, len);
649 pos2 = thiscopy->name;
651 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
653 inquotes = !inquotes;
655 } else *pos2++ = *pos1++;
658 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
660 /* This is either the first source, concatenated subsequent source or destination */
661 if (sourcelist == NULL) {
662 WINE_TRACE("Adding as first source part\n");
663 sourcelist = thiscopy;
664 lastcopyentry = thiscopy;
665 } else if (concatnextfilename) {
666 WINE_TRACE("Adding to source file list to be concatenated\n");
667 lastcopyentry->next = thiscopy;
668 lastcopyentry = thiscopy;
669 } else if (destination == NULL) {
670 destination = thiscopy;
672 /* We have processed sources and destinations and still found more to do - invalid */
673 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
677 concatnextfilename = FALSE;
679 /* We either need to process the rest of the parameter or move to the next */
680 if (*pos1 == '/' || *pos1 == '+') {
684 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
688 /* Ensure we have at least one source file */
690 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
695 /* Default whether automatic overwriting is on. If we are interactive then
696 we prompt by default, otherwise we overwrite by default
697 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
698 if (opt_noty) prompt = TRUE;
699 else if (opt_y) prompt = FALSE;
701 /* By default, we will force the overwrite in batch mode and ask for
702 * confirmation in interactive mode. */
703 prompt = interactive;
704 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
705 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
706 * default behavior. */
707 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
708 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
709 if (!lstrcmpiW (copycmd, parmY))
711 else if (!lstrcmpiW (copycmd, parmNoY))
716 /* Calculate the destination now - if none supplied, its current dir +
717 filename of first file in list*/
718 if (destination == NULL) {
720 WINE_TRACE("No destination supplied, so need to calculate it\n");
721 strcpyW(destname, dotW);
722 strcatW(destname, slashW);
724 destination = heap_alloc(sizeof(COPY_FILES));
725 if (destination == NULL) goto exitreturn;
726 destination->concatenate = FALSE; /* Not used for destination */
727 destination->binarycopy = binarymode;
728 destination->next = NULL; /* Not used for destination */
729 destination->name = NULL; /* To be filled in */
730 destisdirectory = TRUE;
736 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
738 /* Convert to fully qualified path/filename */
739 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
740 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
742 /* If parameter is a directory, ensure it ends in \ */
743 attributes = GetFileAttributesW(destname);
744 if ((destname[strlenW(destname) - 1] == '\\') ||
745 ((attributes != INVALID_FILE_ATTRIBUTES) &&
746 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
748 destisdirectory = TRUE;
749 if (!(destname[strlenW(destname) - 1] == '\\')) strcatW(destname, slashW);
750 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
754 /* Normally, the destination is the current directory unless we are
755 concatenating, in which case its current directory plus first filename.
757 In addition by default it is a binary copy unless concatenating, when
758 the copy defaults to an ascii copy (stop at EOF). We do not know the
759 first source part yet (until we search) so flag as needing filling in. */
762 /* We have found an a+b type syntax, so destination has to be a filename
763 and we need to default to ascii copying. If we have been supplied a
764 directory as the destination, we need to defer calculating the name */
765 if (destisdirectory) appendfirstsource = TRUE;
766 if (destination->binarycopy == -1) destination->binarycopy = 0;
768 } else if (!destisdirectory) {
769 /* We have been asked to copy to a filename. Default to ascii IF the
770 source contains wildcards (true even if only one match) */
771 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
772 anyconcats = TRUE; /* We really are concatenating to a single file */
773 if (destination->binarycopy == -1) {
774 destination->binarycopy = 0;
777 if (destination->binarycopy == -1) {
778 destination->binarycopy = 1;
783 /* Save away the destination name*/
784 heap_free(destination->name);
785 destination->name = heap_strdupW(destname);
786 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
787 wine_dbgstr_w(destname), appendfirstsource);
789 /* Remember if the destination is a device */
790 if (strncmpW(destination->name, deviceW, strlenW(deviceW)) == 0) {
791 WINE_TRACE("Destination is a device\n");
795 /* Now we need to walk the set of sources, and process each name we come to.
796 If anyconcats is true, we are writing to one file, otherwise we are using
797 the source name each time.
798 If destination exists, prompt for overwrite the first time (if concatenating
799 we ask each time until yes is answered)
800 The first source file we come across must exist (when wildcards expanded)
801 and if concatenating with overwrite prompts, each source file must exist
802 until a yes is answered. */
804 thiscopy = sourcelist;
807 while (thiscopy != NULL) {
809 WCHAR srcpath[MAX_PATH];
810 const WCHAR *srcname;
813 BOOL srcisdevice = FALSE;
815 /* If it was not explicit, we now know whether we are concatenating or not and
816 hence whether to copy as binary or ascii */
817 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
819 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
820 to where the filename portion begins (used for wildcart expansion. */
821 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
822 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
824 /* If parameter is a directory, ensure it ends in \* */
825 attributes = GetFileAttributesW(srcpath);
826 if (srcpath[strlenW(srcpath) - 1] == '\\') {
828 /* We need to know where the filename part starts, so append * and
829 recalculate the full resulting path */
830 strcatW(thiscopy->name, starW);
831 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
832 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
834 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
835 (attributes != INVALID_FILE_ATTRIBUTES) &&
836 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
838 /* We need to know where the filename part starts, so append \* and
839 recalculate the full resulting path */
840 strcatW(thiscopy->name, slashstarW);
841 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
842 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
845 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
846 wine_dbgstr_w(srcpath), anyconcats);
848 /* If the source is a device, just use it, otherwise search */
849 if (strncmpW(srcpath, deviceW, strlenW(deviceW)) == 0) {
850 WINE_TRACE("Source is a device\n");
852 srcname = &srcpath[4]; /* After the \\.\ prefix */
855 /* Loop through all source files */
856 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
857 hff = FindFirstFileW(srcpath, &fd);
858 if (hff != INVALID_HANDLE_VALUE) {
859 srcname = fd.cFileName;
863 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
865 WCHAR outname[MAX_PATH];
868 /* Skip . and .., and directories */
869 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
870 WINE_TRACE("Skipping directories\n");
873 /* Build final destination name */
874 strcpyW(outname, destination->name);
875 if (destisdirectory || appendfirstsource) strcatW(outname, srcname);
877 /* Build source name */
878 if (!srcisdevice) strcpyW(filenamepart, srcname);
880 /* Do we just overwrite (we do if we are writing to a device) */
882 if (dstisdevice || (anyconcats && writtenoneconcat)) {
886 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
887 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
888 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
889 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
891 /* Prompt before overwriting */
893 DWORD attributes = GetFileAttributesW(outname);
894 if (attributes != INVALID_FILE_ATTRIBUTES) {
896 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
897 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
900 else overwrite = TRUE;
903 /* If we needed to save away the first filename, do it */
904 if (appendfirstsource && overwrite) {
905 heap_free(destination->name);
906 destination->name = heap_strdupW(outname);
907 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
908 appendfirstsource = FALSE;
909 destisdirectory = FALSE;
912 /* Do the copy as appropriate */
914 if (anyconcats && writtenoneconcat) {
915 if (thiscopy->binarycopy) {
916 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
918 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
920 } else if (!thiscopy->binarycopy) {
921 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
922 } else if (srcisdevice) {
923 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
925 status = CopyFileW(srcpath, outname, FALSE);
931 WINE_TRACE("Copied successfully\n");
932 if (anyconcats) writtenoneconcat = TRUE;
934 /* Append EOF if ascii destination and we are not going to add more onto the end
935 Note: Testing shows windows has an optimization whereas if you have a binary
936 copy of a file to a single destination (ie concatenation) then it does not add
937 the EOF, hence the check on the source copy type below. */
938 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
939 if (!WCMD_AppendEOF(outname)) {
947 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
948 if (!srcisdevice) FindClose (hff);
950 /* Error if the first file was not found */
951 if (!anyconcats || (anyconcats && !writtenoneconcat)) {
957 /* Step on to the next supplied source */
958 thiscopy = thiscopy -> next;
961 /* Append EOF if ascii destination and we were concatenating */
962 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
963 if (!WCMD_AppendEOF(destination->name)) {
969 /* Exit out of the routine, freeing any remaining allocated memory */
972 thiscopy = sourcelist;
973 while (thiscopy != NULL) {
975 /* Free up this block*/
976 thiscopy = thiscopy -> next;
977 heap_free(prevcopy->name);
981 /* Free up the destination memory */
983 heap_free(destination->name);
984 heap_free(destination);
990 /****************************************************************************
993 * Create a directory (and, if needed, any intermediate directories).
995 * Modifies its argument by replacing slashes temporarily with nulls.
998 static BOOL create_full_path(WCHAR* path)
1002 /* don't mess with drive letter portion of path, if any */
1007 /* Strip trailing slashes. */
1008 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
1011 /* Step through path, creating intermediate directories as needed. */
1012 /* First component includes drive letter, if any. */
1016 /* Skip to end of component */
1017 while (*p == '\\') p++;
1018 while (*p && *p != '\\') p++;
1020 /* path is now the original full path */
1021 return CreateDirectoryW(path, NULL);
1023 /* Truncate path, create intermediate directory, and restore path */
1025 rv = CreateDirectoryW(path, NULL);
1027 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1034 void WCMD_create_dir (WCHAR *args) {
1038 if (param1[0] == 0x00) {
1039 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1042 /* Loop through all args */
1044 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1046 if (!create_full_path(thisArg)) {
1047 WCMD_print_error ();
1053 /* Parse the /A options given by the user on the commandline
1054 * into a bitmask of wanted attributes (*wantSet),
1055 * and a bitmask of unwanted attributes (*wantClear).
1057 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1058 static const WCHAR parmA[] = {'/','A','\0'};
1061 /* both are strictly 'out' parameters */
1065 /* For each /A argument */
1066 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1067 /* Skip /A itself */
1070 /* Skip optional : */
1073 /* For each of the attribute specifier chars to this /A option */
1074 for (; *p != 0 && *p != '/'; p++) {
1075 BOOL negate = FALSE;
1083 /* Convert the attribute specifier to a bit in one of the masks */
1085 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1086 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1087 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1088 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1090 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1100 /* If filename part of parameter is * or *.*,
1101 * and neither /Q nor /P options were given,
1102 * prompt the user whether to proceed.
1103 * Returns FALSE if user says no, TRUE otherwise.
1104 * *pPrompted is set to TRUE if the user is prompted.
1105 * (If /P supplied, del will prompt for individual files later.)
1107 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1108 static const WCHAR parmP[] = {'/','P','\0'};
1109 static const WCHAR parmQ[] = {'/','Q','\0'};
1111 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1112 static const WCHAR anyExt[]= {'.','*','\0'};
1114 WCHAR dir[MAX_PATH];
1115 WCHAR fname[MAX_PATH];
1116 WCHAR ext[MAX_PATH];
1117 WCHAR fpath[MAX_PATH];
1119 /* Convert path into actual directory spec */
1120 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1121 WCMD_splitpath(fpath, drive, dir, fname, ext);
1123 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1124 if ((strcmpW(fname, starW) == 0) &&
1125 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1127 WCHAR question[MAXSTRING];
1128 static const WCHAR fmt[] = {'%','s',' ','\0'};
1130 /* Caller uses this to suppress "file not found" warning later */
1133 /* Ask for confirmation */
1134 wsprintfW(question, fmt, fpath);
1135 return WCMD_ask_confirm(question, TRUE, NULL);
1138 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1142 /* Helper function for WCMD_delete().
1143 * Deletes a single file, directory, or wildcard.
1144 * If /S was given, does it recursively.
1145 * Returns TRUE if a file was deleted.
1147 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1149 static const WCHAR parmP[] = {'/','P','\0'};
1150 static const WCHAR parmS[] = {'/','S','\0'};
1151 static const WCHAR parmF[] = {'/','F','\0'};
1153 DWORD unwanted_attrs;
1155 WCHAR argCopy[MAX_PATH];
1156 WIN32_FIND_DATAW fd;
1158 WCHAR fpath[MAX_PATH];
1160 BOOL handleParm = TRUE;
1162 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1164 strcpyW(argCopy, thisArg);
1165 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1166 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1168 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1169 /* Skip this arg if user declines to delete *.* */
1173 /* First, try to delete in the current directory */
1174 hff = FindFirstFileW(argCopy, &fd);
1175 if (hff == INVALID_HANDLE_VALUE) {
1181 /* Support del <dirname> by just deleting all files dirname\* */
1183 && (strchrW(argCopy,'*') == NULL)
1184 && (strchrW(argCopy,'?') == NULL)
1185 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1187 WCHAR modifiedParm[MAX_PATH];
1188 static const WCHAR slashStar[] = {'\\','*','\0'};
1190 strcpyW(modifiedParm, argCopy);
1191 strcatW(modifiedParm, slashStar);
1194 WCMD_delete_one(modifiedParm);
1196 } else if (handleParm) {
1198 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1199 strcpyW (fpath, argCopy);
1201 p = strrchrW (fpath, '\\');
1204 strcatW (fpath, fd.cFileName);
1206 else strcpyW (fpath, fd.cFileName);
1207 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1210 /* Handle attribute matching (/A) */
1211 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1212 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1214 /* /P means prompt for each file */
1215 if (ok && strstrW (quals, parmP) != NULL) {
1218 /* Ask for confirmation */
1219 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1220 ok = WCMD_ask_confirm(question, FALSE, NULL);
1221 LocalFree(question);
1224 /* Only proceed if ok to */
1227 /* If file is read only, and /A:r or /F supplied, delete it */
1228 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1229 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1230 strstrW (quals, parmF) != NULL)) {
1231 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1234 /* Now do the delete */
1235 if (!DeleteFileW(fpath)) WCMD_print_error ();
1239 } while (FindNextFileW(hff, &fd) != 0);
1243 /* Now recurse into all subdirectories handling the parameter in the same way */
1244 if (strstrW (quals, parmS) != NULL) {
1246 WCHAR thisDir[MAX_PATH];
1250 WCHAR dir[MAX_PATH];
1251 WCHAR fname[MAX_PATH];
1252 WCHAR ext[MAX_PATH];
1254 /* Convert path into actual directory spec */
1255 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1256 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1258 strcpyW(thisDir, drive);
1259 strcatW(thisDir, dir);
1260 cPos = strlenW(thisDir);
1262 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1264 /* Append '*' to the directory */
1265 thisDir[cPos] = '*';
1266 thisDir[cPos+1] = 0x00;
1268 hff = FindFirstFileW(thisDir, &fd);
1270 /* Remove residual '*' */
1271 thisDir[cPos] = 0x00;
1273 if (hff != INVALID_HANDLE_VALUE) {
1274 DIRECTORY_STACK *allDirs = NULL;
1275 DIRECTORY_STACK *lastEntry = NULL;
1278 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1279 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1280 (strcmpW(fd.cFileName, dotW) != 0)) {
1282 DIRECTORY_STACK *nextDir;
1283 WCHAR subParm[MAX_PATH];
1285 /* Work out search parameter in sub dir */
1286 strcpyW (subParm, thisDir);
1287 strcatW (subParm, fd.cFileName);
1288 strcatW (subParm, slashW);
1289 strcatW (subParm, fname);
1290 strcatW (subParm, ext);
1291 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1293 /* Allocate memory, add to list */
1294 nextDir = heap_alloc(sizeof(DIRECTORY_STACK));
1295 if (allDirs == NULL) allDirs = nextDir;
1296 if (lastEntry != NULL) lastEntry->next = nextDir;
1297 lastEntry = nextDir;
1298 nextDir->next = NULL;
1299 nextDir->dirName = heap_strdupW(subParm);
1301 } while (FindNextFileW(hff, &fd) != 0);
1304 /* Go through each subdir doing the delete */
1305 while (allDirs != NULL) {
1306 DIRECTORY_STACK *tempDir;
1308 tempDir = allDirs->next;
1309 found |= WCMD_delete_one (allDirs->dirName);
1311 heap_free(allDirs->dirName);
1321 /****************************************************************************
1324 * Delete a file or wildcarded set.
1327 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1328 * - Each set is a pattern, eg /ahr /as-r means
1329 * readonly+hidden OR nonreadonly system files
1330 * - The '-' applies to a single field, ie /a:-hr means read only
1334 BOOL WCMD_delete (WCHAR *args) {
1337 BOOL argsProcessed = FALSE;
1338 BOOL foundAny = FALSE;
1342 for (argno=0; ; argno++) {
1347 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1349 break; /* no more parameters */
1351 continue; /* skip options */
1353 argsProcessed = TRUE;
1354 found = WCMD_delete_one(thisArg);
1357 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1362 /* Handle no valid args */
1364 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1372 * Returns a trimmed version of s with all leading and trailing whitespace removed
1376 static WCHAR *WCMD_strtrim(const WCHAR *s)
1378 DWORD len = strlenW(s);
1379 const WCHAR *start = s;
1382 result = heap_alloc((len + 1) * sizeof(WCHAR));
1384 while (isspaceW(*start)) start++;
1386 const WCHAR *end = s + len - 1;
1387 while (end > start && isspaceW(*end)) end--;
1388 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1389 result[end - start + 1] = '\0';
1397 /****************************************************************************
1400 * Echo input to the screen (or not). We don't try to emulate the bugs
1401 * in DOS (try typing "ECHO ON AGAIN" for an example).
1404 void WCMD_echo (const WCHAR *args)
1407 const WCHAR *origcommand = args;
1410 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1411 || args[0]==':' || args[0]==';')
1414 trimmed = WCMD_strtrim(args);
1415 if (!trimmed) return;
1417 count = strlenW(trimmed);
1418 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1419 && origcommand[0]!=';') {
1420 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1421 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1426 if (lstrcmpiW(trimmed, onW) == 0)
1428 else if (lstrcmpiW(trimmed, offW) == 0)
1431 WCMD_output_asis (args);
1432 WCMD_output_asis (newlineW);
1437 /*****************************************************************************
1440 * Execute a command, and any && or bracketed follow on to the command. The
1441 * first command to be executed may not be at the front of the
1442 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1444 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1445 BOOL isIF, BOOL executecmds)
1447 CMD_LIST *curPosition = *cmdList;
1448 int myDepth = (*cmdList)->bracketDepth;
1450 WINE_TRACE("cmdList(%p), firstCmd(%p), doIt(%d)\n",
1451 cmdList, wine_dbgstr_w(firstcmd),
1454 /* Skip leading whitespace between condition and the command */
1455 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1457 /* Process the first command, if there is one */
1458 if (executecmds && firstcmd && *firstcmd) {
1459 WCHAR *command = heap_strdupW(firstcmd);
1460 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1465 /* If it didn't move the position, step to next command */
1466 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1468 /* Process any other parts of the command */
1470 BOOL processThese = executecmds;
1473 static const WCHAR ifElse[] = {'e','l','s','e'};
1475 /* execute all appropriate commands */
1476 curPosition = *cmdList;
1478 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1480 (*cmdList)->prevDelim,
1481 (*cmdList)->bracketDepth, myDepth);
1483 /* Execute any statements appended to the line */
1484 /* FIXME: Only if previous call worked for && or failed for || */
1485 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1486 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1487 if (processThese && (*cmdList)->command) {
1488 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1491 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1493 /* Execute any appended to the statement with (...) */
1494 } else if ((*cmdList)->bracketDepth > myDepth) {
1496 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1497 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1499 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1501 /* End of the command - does 'ELSE ' follow as the next command? */
1504 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1505 (*cmdList)->command)) {
1507 /* Swap between if and else processing */
1508 processThese = !processThese;
1510 /* Process the ELSE part */
1512 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1513 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1515 /* Skip leading whitespace between condition and the command */
1516 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1518 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1521 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1523 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1532 /*****************************************************************************
1533 * WCMD_parse_forf_options
1535 * Parses the for /f 'options', extracting the values and validating the
1536 * keywords. Note all keywords are optional.
1538 * options [I] The unparsed parameter string
1539 * eol [O] Set to the comment character (eol=x)
1540 * skip [O] Set to the number of lines to skip (skip=xx)
1541 * delims [O] Set to the token delimiters (delims=)
1542 * tokens [O] Set to the requested tokens, as provided (tokens=)
1543 * usebackq [O] Set to TRUE if usebackq found
1545 * Returns TRUE on success, FALSE on syntax error
1548 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1549 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1552 WCHAR *pos = options;
1553 int len = strlenW(pos);
1554 static const WCHAR eolW[] = {'e','o','l','='};
1555 static const WCHAR skipW[] = {'s','k','i','p','='};
1556 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1557 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1558 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1559 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1560 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1562 /* Initialize to defaults */
1563 strcpyW(delims, forf_defaultdelims);
1564 strcpyW(tokens, forf_defaulttokens);
1569 /* Strip (optional) leading and trailing quotes */
1570 if ((*pos == '"') && (pos[len-1] == '"')) {
1575 /* Process each keyword */
1576 while (pos && *pos) {
1577 if (*pos == ' ' || *pos == '\t') {
1580 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1581 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1582 pos, sizeof(eolW)/sizeof(WCHAR),
1583 eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1584 *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1585 pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1586 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1588 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1589 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1590 pos, sizeof(skipW)/sizeof(WCHAR),
1591 skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1592 WCHAR *nextchar = NULL;
1593 pos = pos + sizeof(skipW)/sizeof(WCHAR);
1594 *skip = strtoulW(pos, &nextchar, 0);
1595 WINE_TRACE("Found skip as %d lines\n", *skip);
1598 /* Save if usebackq semantics are in effect */
1599 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1600 pos, sizeof(usebackqW)/sizeof(WCHAR),
1601 usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1603 pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1604 WINE_TRACE("Found usebackq\n");
1606 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1607 if you finish the optionsroot string with delims= otherwise the space is
1608 just a token delimiter! */
1609 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1610 pos, sizeof(delimsW)/sizeof(WCHAR),
1611 delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1614 pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1615 while (*pos && *pos != ' ') {
1619 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1620 delims[i++] = 0; /* Null terminate the delims */
1621 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1623 /* Save the tokens being requested */
1624 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1625 pos, sizeof(tokensW)/sizeof(WCHAR),
1626 tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1629 pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1630 while (*pos && *pos != ' ') {
1634 tokens[i++] = 0; /* Null terminate the tokens */
1635 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1638 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1645 /*****************************************************************************
1646 * WCMD_add_dirstowalk
1648 * When recursing through directories (for /r), we need to add to the list of
1649 * directories still to walk, any subdirectories of the one we are processing.
1652 * options [I] The remaining list of directories still to process
1654 * Note this routine inserts the subdirectories found between the entry being
1655 * processed, and any other directory still to be processed, mimicing what
1658 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1659 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1660 WCHAR fullitem[MAX_PATH];
1661 WIN32_FIND_DATAW fd;
1664 /* Build a generic search and add all directories on the list of directories
1666 strcpyW(fullitem, dirsToWalk->dirName);
1667 strcatW(fullitem, slashstarW);
1668 hff = FindFirstFileW(fullitem, &fd);
1669 if (hff != INVALID_HANDLE_VALUE) {
1671 WINE_TRACE("Looking for subdirectories\n");
1672 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1673 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1674 (strcmpW(fd.cFileName, dotW) != 0))
1676 /* Allocate memory, add to list */
1677 DIRECTORY_STACK *toWalk = heap_alloc(sizeof(DIRECTORY_STACK));
1678 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1679 toWalk->next = remainingDirs->next;
1680 remainingDirs->next = toWalk;
1681 remainingDirs = toWalk;
1682 toWalk->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1683 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1684 strcatW(toWalk->dirName, slashW);
1685 strcatW(toWalk->dirName, fd.cFileName);
1686 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1687 toWalk, toWalk->next);
1689 } while (FindNextFileW(hff, &fd) != 0);
1690 WINE_TRACE("Finished adding all subdirectories\n");
1695 /**************************************************************************
1696 * WCMD_for_nexttoken
1698 * Parse the token= line, identifying the next highest number not processed
1699 * so far. Count how many tokens are referred (including duplicates) and
1700 * optionally return that, plus optionally indicate if the tokens= line
1704 * lasttoken [I] - Identifies the token index of the last one
1705 * returned so far (-1 used for first loop)
1706 * tokenstr [I] - The specified tokens= line
1707 * firstCmd [O] - Optionally indicate how many tokens are listed
1708 * doAll [O] - Optionally indicate if line ends with *
1709 * duplicates [O] - Optionally indicate if there is any evidence of
1710 * overlaying tokens in the string
1711 * Note the caller should keep a running track of duplicates as the tokens
1712 * are recursively passed. If any have duplicates, then the * token should
1715 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1716 int *totalfound, BOOL *doall,
1719 WCHAR *pos = tokenstr;
1722 if (totalfound) *totalfound = 0;
1723 if (doall) *doall = FALSE;
1724 if (duplicates) *duplicates = FALSE;
1726 WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken,
1727 wine_dbgstr_w(tokenstr), nexttoken);
1729 /* Loop through the token string, parsing it. Valid syntax is:
1730 token=m or x-y with comma delimiter and optionally * to finish*/
1732 int nextnumber1, nextnumber2 = -1;
1735 /* Get the next number */
1736 nextnumber1 = strtoulW(pos, &nextchar, 10);
1738 /* If it is followed by a minus, its a range, so get the next one as well */
1739 if (*nextchar == '-') {
1740 nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1742 /* We want to return the lowest number that is higher than lasttoken
1743 but only if range is positive */
1744 if (nextnumber2 >= nextnumber1 &&
1745 lasttoken < nextnumber2) {
1748 if (nexttoken == -1) {
1749 nextvalue = max(nextnumber1, (lasttoken+1));
1751 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1754 /* Flag if duplicates identified */
1755 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1757 nexttoken = nextvalue;
1760 /* Update the running total for the whole range */
1761 if (nextnumber2 >= nextnumber1 && totalfound) {
1762 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1766 if (totalfound) (*totalfound)++;
1768 /* See if the number found is one we have already seen */
1769 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1771 /* We want to return the lowest number that is higher than lasttoken */
1772 if (lasttoken < nextnumber1 &&
1773 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1774 nexttoken = nextnumber1;
1779 /* Remember if it is followed by a star, and if it is indicate a need to
1780 show all tokens, unless a duplicate has been found */
1781 if (*nextchar == '*') {
1782 if (doall) *doall = TRUE;
1783 if (totalfound) (*totalfound)++;
1786 /* Step on to the next character */
1792 if (nexttoken == -1) nexttoken = lasttoken;
1793 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1794 if (totalfound) WINE_TRACE("Found total tokens in total %d\n", *totalfound);
1795 if (doall && *doall) WINE_TRACE("Request for all tokens found\n");
1796 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1800 /**************************************************************************
1803 * When parsing file or string contents (for /f), once the string to parse
1804 * has been identified, handle the various options and call the do part
1808 * cmdStart [I] - Identifies the list of commands making up the
1809 * for loop body (especially if brackets in use)
1810 * firstCmd [I] - The textual start of the command after the DO
1811 * which is within the first item of cmdStart
1812 * cmdEnd [O] - Identifies where to continue after the DO
1813 * variable [I] - The variable identified on the for line
1814 * buffer [I] - The string to parse
1815 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1816 * forf_skip [I/O] - How many lines to skip first
1817 * forf_eol [I] - The 'end of line' (comment) character
1818 * forf_delims [I] - The delimiters to use when breaking the string apart
1819 * forf_tokens [I] - The tokens to use when breaking the string apart
1821 static void WCMD_parse_line(CMD_LIST *cmdStart,
1822 const WCHAR *firstCmd,
1824 const WCHAR variable,
1830 WCHAR *forf_tokens) {
1833 FOR_CONTEXT oldcontext;
1834 int varidx, varoffset;
1835 int nexttoken, lasttoken = -1;
1836 BOOL starfound = FALSE;
1837 BOOL thisduplicate = FALSE;
1838 BOOL anyduplicates = FALSE;
1841 /* Skip lines if requested */
1847 /* Save away any existing for variable context (e.g. nested for loops) */
1848 oldcontext = forloopcontext;
1850 /* Extract the parameters based on the tokens= value (There will always
1851 be some value, as if it is not supplied, it defaults to tokens=1).
1853 Count how many tokens are named in the line, identify the lowest
1854 Empty (set to null terminated string) that number of named variables
1855 While lasttoken != nextlowest
1856 %letter = parameter number 'nextlowest'
1857 letter++ (if >26 or >52 abort)
1858 Go through token= string finding next lowest number
1859 If token ends in * set %letter = raw position of token(nextnumber+1)
1862 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1863 NULL, &thisduplicate);
1864 varidx = FOR_VAR_IDX(variable);
1866 /* Empty out variables */
1868 varidx >= 0 && varoffset<totalfound && ((varidx+varoffset)%26);
1870 forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
1871 /* Stop if we walk beyond z or Z */
1872 if (((varidx+varoffset) % 26) == 0) break;
1875 /* Loop extracting the tokens */
1877 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1878 while (varidx >= 0 && (nexttoken > lasttoken)) {
1879 anyduplicates |= thisduplicate;
1881 /* Extract the token number requested and set into the next variable context */
1882 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, FALSE, FALSE, forf_delims);
1883 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1884 varidx + varoffset, wine_dbgstr_w(parm));
1886 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1888 if (((varidx + varoffset) %26) == 0) break;
1891 /* Find the next token */
1892 lasttoken = nexttoken;
1893 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1894 &starfound, &thisduplicate);
1897 /* If all the rest of the tokens were requested, and there is still space in
1898 the variable range, write them now */
1899 if (!anyduplicates && starfound && varidx >= 0 && ((varidx+varoffset) % 26)) {
1901 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
1902 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
1903 varidx + varoffset, wine_dbgstr_w(parm));
1904 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1907 /* Execute the body of the foor loop with these values */
1908 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
1909 CMD_LIST *thisCmdStart = cmdStart;
1911 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
1912 *cmdEnd = thisCmdStart;
1915 /* Free the duplicated strings, and restore the context */
1918 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
1919 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
1920 (forloopcontext.variable[i] != nullW)) {
1921 heap_free(forloopcontext.variable[i]);
1926 /* Restore the original for variable contextx */
1927 forloopcontext = oldcontext;
1930 /**************************************************************************
1931 * WCMD_forf_getinputhandle
1933 * Return a file handle which can be used for reading the input lines,
1934 * either to a specific file (which may be quote delimited as we have to
1935 * read the parameters in raw mode) or to a command which we need to
1936 * execute. The command being executed runs in its own shell and stores
1937 * its data in a temporary file.
1940 * usebackq [I] - Indicates whether usebackq is in effect or not
1941 * itemStr [I] - The item to be handled, either a filename or
1942 * whole command string to execute
1943 * iscmd [I] - Identifies whether this is a command or not
1945 * Returns a file handle which can be used to read the input lines from.
1947 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
1948 WCHAR temp_str[MAX_PATH];
1949 WCHAR temp_file[MAX_PATH];
1950 WCHAR temp_cmd[MAXSTRING];
1951 HANDLE hinput = INVALID_HANDLE_VALUE;
1952 static const WCHAR redirOutW[] = {'>','%','s','\0'};
1953 static const WCHAR cmdW[] = {'C','M','D','\0'};
1954 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
1955 '/','C',' ','"','%','s','"','\0'};
1957 /* Remove leading and trailing character */
1958 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
1959 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
1960 (!iscmd && (itemstr[0] == '"' && usebackq)))
1962 itemstr[strlenW(itemstr)-1] = 0x00;
1967 /* Get temp filename */
1968 GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
1969 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
1971 /* Redirect output to the temporary file */
1972 wsprintfW(temp_str, redirOutW, temp_file);
1973 wsprintfW(temp_cmd, cmdslashcW, itemstr);
1974 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
1975 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
1976 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
1978 /* Open the file, read line by line and process */
1979 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1980 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
1983 /* Open the file, read line by line and process */
1984 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
1985 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
1986 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1991 /**************************************************************************
1994 * Batch file loop processing.
1996 * On entry: cmdList contains the syntax up to the set
1997 * next cmdList and all in that bracket contain the set data
1998 * next cmdlist contains the DO cmd
1999 * following that is either brackets or && entries (as per if)
2003 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2005 WIN32_FIND_DATAW fd;
2008 static const WCHAR inW[] = {'i','n'};
2009 static const WCHAR doW[] = {'d','o'};
2010 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2013 WCHAR *oldvariablevalue;
2016 WCHAR optionsRoot[MAX_PATH];
2017 DIRECTORY_STACK *dirsToWalk = NULL;
2018 BOOL expandDirs = FALSE;
2019 BOOL useNumbers = FALSE;
2020 BOOL doFileset = FALSE;
2021 BOOL doRecurse = FALSE;
2022 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2023 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2025 CMD_LIST *thisCmdStart;
2026 int parameterNo = 0;
2029 WCHAR forf_delims[256];
2030 WCHAR forf_tokens[MAXSTRING];
2031 BOOL forf_usebackq = FALSE;
2033 /* Handle optional qualifiers (multiple are allowed) */
2034 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2037 while (thisArg && *thisArg == '/') {
2038 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2040 switch (toupperW(*thisArg)) {
2041 case 'D': expandDirs = TRUE; break;
2042 case 'L': useNumbers = TRUE; break;
2044 /* Recursive is special case - /R can have an optional path following it */
2045 /* filenamesets are another special case - /F can have an optional options following it */
2049 /* When recursing directories, use current directory as the starting point unless
2050 subsequently overridden */
2051 doRecurse = (toupperW(*thisArg) == 'R');
2052 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
2054 doFileset = (toupperW(*thisArg) == 'F');
2056 /* Retrieve next parameter to see if is root/options (raw form required
2057 with for /f, or unquoted in for /r) */
2058 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2060 /* Next parm is either qualifier, path/options or variable -
2061 only care about it if it is the path/options */
2062 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2064 strcpyW(optionsRoot, thisArg);
2069 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2072 /* Step to next token */
2073 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2076 /* Ensure line continues with variable */
2077 if (!*thisArg || *thisArg != '%') {
2078 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2082 /* With for /f parse the options if provided */
2084 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2085 forf_delims, forf_tokens, &forf_usebackq))
2087 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2091 /* Set up the list of directories to recurse if we are going to */
2092 } else if (doRecurse) {
2093 /* Allocate memory, add to list */
2094 dirsToWalk = heap_alloc(sizeof(DIRECTORY_STACK));
2095 dirsToWalk->next = NULL;
2096 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2097 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2100 /* Variable should follow */
2101 strcpyW(variable, thisArg);
2102 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2103 varidx = FOR_VAR_IDX(variable[1]);
2105 /* Ensure line continues with IN */
2106 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2108 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2109 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
2110 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
2111 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2115 /* Save away where the set of data starts and the variable */
2116 thisDepth = (*cmdList)->bracketDepth;
2117 *cmdList = (*cmdList)->nextcommand;
2118 setStart = (*cmdList);
2120 /* Skip until the close bracket */
2121 WINE_TRACE("Searching %p as the set\n", *cmdList);
2123 (*cmdList)->command != NULL &&
2124 (*cmdList)->bracketDepth > thisDepth) {
2125 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2126 *cmdList = (*cmdList)->nextcommand;
2129 /* Skip the close bracket, if there is one */
2130 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2132 /* Syntax error if missing close bracket, or nothing following it
2133 and once we have the complete set, we expect a DO */
2134 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2135 if ((*cmdList == NULL)
2136 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
2138 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2144 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2145 mode, or once for the rest of the time. */
2148 /* Save away the starting position for the commands (and offset for the
2150 cmdStart = *cmdList;
2151 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2154 /* If we are recursing directories (ie /R), add all sub directories now, then
2155 prefix the root when searching for the item */
2156 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2159 /* Loop through all set entries */
2161 thisSet->command != NULL &&
2162 thisSet->bracketDepth >= thisDepth) {
2164 /* Loop through all entries on the same line */
2167 WCHAR buffer[MAXSTRING];
2169 WINE_TRACE("Processing for set %p\n", thisSet);
2171 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2174 * If the parameter within the set has a wildcard then search for matching files
2175 * otherwise do a literal substitution.
2177 static const WCHAR wildcards[] = {'*','?','\0'};
2178 thisCmdStart = cmdStart;
2181 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2183 if (!useNumbers && !doFileset) {
2184 WCHAR fullitem[MAX_PATH];
2186 /* Now build the item to use / search for in the specified directory,
2187 as it is fully qualified in the /R case */
2189 strcpyW(fullitem, dirsToWalk->dirName);
2190 strcatW(fullitem, slashW);
2191 strcatW(fullitem, item);
2193 strcpyW(fullitem, item);
2196 if (strpbrkW (fullitem, wildcards)) {
2198 hff = FindFirstFileW(fullitem, &fd);
2199 if (hff != INVALID_HANDLE_VALUE) {
2201 BOOL isDirectory = FALSE;
2203 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2205 /* Handle as files or dirs appropriately, but ignore . and .. */
2206 if (isDirectory == expandDirs &&
2207 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2208 (strcmpW(fd.cFileName, dotW) != 0))
2210 thisCmdStart = cmdStart;
2211 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2214 strcpyW(fullitem, dirsToWalk->dirName);
2215 strcatW(fullitem, slashW);
2216 strcatW(fullitem, fd.cFileName);
2218 strcpyW(fullitem, fd.cFileName);
2222 /* Save away any existing for variable context (e.g. nested for loops)
2223 and restore it after executing the body of this for loop */
2225 oldvariablevalue = forloopcontext.variable[varidx];
2226 forloopcontext.variable[varidx] = fullitem;
2228 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2229 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2231 cmdEnd = thisCmdStart;
2233 } while (FindNextFileW(hff, &fd) != 0);
2239 /* Save away any existing for variable context (e.g. nested for loops)
2240 and restore it after executing the body of this for loop */
2242 oldvariablevalue = forloopcontext.variable[varidx];
2243 forloopcontext.variable[varidx] = fullitem;
2245 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2246 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2248 cmdEnd = thisCmdStart;
2251 } else if (useNumbers) {
2252 /* Convert the first 3 numbers to signed longs and save */
2253 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2254 /* else ignore them! */
2256 /* Filesets - either a list of files, or a command to run and parse the output */
2257 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2258 (forf_usebackq && *itemStart != '\''))) {
2263 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2264 wine_dbgstr_w(item));
2266 /* If backquote or single quote, we need to launch that command
2267 and parse the results - use a temporary file */
2268 if ((forf_usebackq && *itemStart == '`') ||
2269 (!forf_usebackq && *itemStart == '\'')) {
2271 /* Use itemstart because the command is the whole set, not just the first token */
2272 itemparm = itemStart;
2275 /* Use item because the file to process is just the first item in the set */
2278 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2280 /* Process the input file */
2281 if (input == INVALID_HANDLE_VALUE) {
2282 WCMD_print_error ();
2283 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2285 return; /* FOR loop aborts at first failure here */
2289 /* Read line by line until end of file */
2290 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2291 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2292 &forf_skip, forf_eol, forf_delims, forf_tokens);
2295 CloseHandle (input);
2298 /* When we have processed the item as a whole command, abort future set processing */
2299 if (itemparm==itemStart) {
2304 /* Filesets - A string literal */
2305 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2306 (forf_usebackq && *itemStart == '\''))) {
2308 /* Remove leading and trailing character, ready to parse with delims= delimiters
2309 Note that the last quote is removed from the set and the string terminates
2310 there to mimic windows */
2311 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2317 /* Copy the item away from the global buffer used by WCMD_parameter */
2318 strcpyW(buffer, itemStart);
2319 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2320 &forf_skip, forf_eol, forf_delims, forf_tokens);
2322 /* Only one string can be supplied in the whole set, abort future set processing */
2327 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2331 /* Move onto the next set line */
2332 if (thisSet) thisSet = thisSet->nextcommand;
2335 /* If /L is provided, now run the for loop */
2338 static const WCHAR fmt[] = {'%','d','\0'};
2340 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2341 numbers[0], numbers[2], numbers[1]);
2343 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2346 sprintfW(thisNum, fmt, i);
2347 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2349 thisCmdStart = cmdStart;
2352 /* Save away any existing for variable context (e.g. nested for loops)
2353 and restore it after executing the body of this for loop */
2355 oldvariablevalue = forloopcontext.variable[varidx];
2356 forloopcontext.variable[varidx] = thisNum;
2358 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2359 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2361 cmdEnd = thisCmdStart;
2364 /* If we are walking directories, move on to any which remain */
2365 if (dirsToWalk != NULL) {
2366 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2367 heap_free(dirsToWalk->dirName);
2368 heap_free(dirsToWalk);
2369 dirsToWalk = nextDir;
2370 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2371 wine_dbgstr_w(dirsToWalk->dirName));
2372 else WINE_TRACE("Finished all directories.\n");
2375 } while (dirsToWalk != NULL);
2377 /* Now skip over the do part if we did not perform the for loop so far.
2378 We store in cmdEnd the next command after the do block, but we only
2379 know this if something was run. If it has not been, we need to calculate
2382 thisCmdStart = cmdStart;
2383 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2384 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2385 cmdEnd = thisCmdStart;
2388 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2389 all processing, OR it should be pointing to the end of && processing OR
2390 it should be pointing at the NULL end of bracket for the DO. The return
2391 value needs to be the NEXT command to execute, which it either is, or
2392 we need to step over the closing bracket */
2394 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2397 /**************************************************************************
2400 * Simple on-line help. Help text is stored in the resource file.
2403 void WCMD_give_help (const WCHAR *args)
2407 args = WCMD_skip_leading_spaces((WCHAR*) args);
2408 if (strlenW(args) == 0) {
2409 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2412 /* Display help message for builtin commands */
2413 for (i=0; i<=WCMD_EXIT; i++) {
2414 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2415 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2416 WCMD_output_asis (WCMD_LoadMessage(i));
2420 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2421 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2422 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2423 args, -1, externals[i], -1) == CSTR_EQUAL) {
2425 static const WCHAR helpW[] = {' ', '/','?','\0'};
2427 strcatW(cmd, helpW);
2428 WCMD_run_program(cmd, FALSE);
2432 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2437 /****************************************************************************
2440 * Batch file jump instruction. Not the most efficient algorithm ;-)
2441 * Prints error message if the specified label cannot be found - the file pointer is
2442 * then at EOF, effectively stopping the batch file.
2443 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2446 void WCMD_goto (CMD_LIST **cmdList) {
2448 WCHAR string[MAX_PATH];
2449 WCHAR current[MAX_PATH];
2451 /* Do not process any more parts of a processed multipart or multilines command */
2452 if (cmdList) *cmdList = NULL;
2454 if (context != NULL) {
2455 WCHAR *paramStart = param1, *str;
2456 static const WCHAR eofW[] = {':','e','o','f','\0'};
2458 if (param1[0] == 0x00) {
2459 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2463 /* Handle special :EOF label */
2464 if (lstrcmpiW (eofW, param1) == 0) {
2465 context -> skip_rest = TRUE;
2469 /* Support goto :label as well as goto label */
2470 if (*paramStart == ':') paramStart++;
2472 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2473 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2475 while (isspaceW (*str)) str++;
2479 while (((current[index] = str[index])) && (!isspaceW (current[index])))
2482 /* ignore space at the end */
2484 if (lstrcmpiW (current, paramStart) == 0) return;
2487 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2492 /*****************************************************************************
2495 * Push a directory onto the stack
2498 void WCMD_pushd (const WCHAR *args)
2500 struct env_stack *curdir;
2502 static const WCHAR parmD[] = {'/','D','\0'};
2504 if (strchrW(args, '/') != NULL) {
2505 SetLastError(ERROR_INVALID_PARAMETER);
2510 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2511 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2512 if( !curdir || !thisdir ) {
2515 WINE_ERR ("out of memory\n");
2519 /* Change directory using CD code with /D parameter */
2520 strcpyW(quals, parmD);
2521 GetCurrentDirectoryW (1024, thisdir);
2523 WCMD_setshow_default(args);
2529 curdir -> next = pushd_directories;
2530 curdir -> strings = thisdir;
2531 if (pushd_directories == NULL) {
2532 curdir -> u.stackdepth = 1;
2534 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2536 pushd_directories = curdir;
2541 /*****************************************************************************
2544 * Pop a directory from the stack
2547 void WCMD_popd (void) {
2548 struct env_stack *temp = pushd_directories;
2550 if (!pushd_directories)
2553 /* pop the old environment from the stack, and make it the current dir */
2554 pushd_directories = temp->next;
2555 SetCurrentDirectoryW(temp->strings);
2556 LocalFree (temp->strings);
2560 /*******************************************************************
2561 * evaluate_if_comparison
2563 * Evaluates an "if" comparison operation
2566 * leftOperand [I] left operand, non NULL
2567 * operator [I] "if" binary comparison operator, non NULL
2568 * rightOperand [I] right operand, non NULL
2569 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2572 * Success: 1 if operator applied to the operands evaluates to TRUE
2573 * 0 if operator applied to the operands evaluates to FALSE
2574 * Failure: -1 if operator is not recognized
2576 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2577 const WCHAR *rightOperand, int caseInsensitive)
2579 WCHAR *endptr_leftOp, *endptr_rightOp;
2580 long int leftOperand_int, rightOperand_int;
2582 static const WCHAR lssW[] = {'l','s','s','\0'};
2583 static const WCHAR leqW[] = {'l','e','q','\0'};
2584 static const WCHAR equW[] = {'e','q','u','\0'};
2585 static const WCHAR neqW[] = {'n','e','q','\0'};
2586 static const WCHAR geqW[] = {'g','e','q','\0'};
2587 static const WCHAR gtrW[] = {'g','t','r','\0'};
2589 /* == is a special case, as it always compares strings */
2590 if (!lstrcmpiW(operator, eqeqW))
2591 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2592 : lstrcmpW (leftOperand, rightOperand) == 0;
2594 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2595 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2596 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2597 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2599 /* Perform actual (integer or string) comparison */
2600 if (!lstrcmpiW(operator, lssW)) {
2602 return leftOperand_int < rightOperand_int;
2604 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2605 : lstrcmpW (leftOperand, rightOperand) < 0;
2608 if (!lstrcmpiW(operator, leqW)) {
2610 return leftOperand_int <= rightOperand_int;
2612 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2613 : lstrcmpW (leftOperand, rightOperand) <= 0;
2616 if (!lstrcmpiW(operator, equW)) {
2618 return leftOperand_int == rightOperand_int;
2620 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2621 : lstrcmpW (leftOperand, rightOperand) == 0;
2624 if (!lstrcmpiW(operator, neqW)) {
2626 return leftOperand_int != rightOperand_int;
2628 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2629 : lstrcmpW (leftOperand, rightOperand) != 0;
2632 if (!lstrcmpiW(operator, geqW)) {
2634 return leftOperand_int >= rightOperand_int;
2636 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2637 : lstrcmpW (leftOperand, rightOperand) >= 0;
2640 if (!lstrcmpiW(operator, gtrW)) {
2642 return leftOperand_int > rightOperand_int;
2644 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2645 : lstrcmpW (leftOperand, rightOperand) > 0;
2651 /****************************************************************************
2654 * Batch file conditional.
2656 * On entry, cmdlist will point to command containing the IF, and optionally
2657 * the first command to execute (if brackets not found)
2658 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2659 * If ('s were found, execute all within that bracket
2660 * Command may optionally be followed by an ELSE - need to skip instructions
2661 * in the else using the same logic
2663 * FIXME: Much more syntax checking needed!
2665 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2667 int negate; /* Negate condition */
2668 int test; /* Condition evaluation result */
2669 WCHAR condition[MAX_PATH], *command;
2670 static const WCHAR notW[] = {'n','o','t','\0'};
2671 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2672 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2673 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2674 static const WCHAR parmI[] = {'/','I','\0'};
2675 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2677 negate = !lstrcmpiW(param1,notW);
2678 strcpyW(condition, (negate ? param2 : param1));
2679 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2681 if (!lstrcmpiW (condition, errlvlW)) {
2682 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2684 long int param_int = strtolW(param, &endptr, 10);
2685 if (*endptr) goto syntax_err;
2686 test = ((long int)errorlevel >= param_int);
2687 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2689 else if (!lstrcmpiW (condition, existW)) {
2690 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2691 != INVALID_FILE_ATTRIBUTES);
2692 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2694 else if (!lstrcmpiW (condition, defdW)) {
2695 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2697 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2699 else { /* comparison operation */
2700 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2703 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, ¶mStart, TRUE, FALSE));
2707 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2708 p = paramStart + strlenW(leftOperand);
2709 while (*p == ' ' || *p == '\t')
2712 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2713 strcpyW(operator, eqeqW);
2715 strcpyW(operator, WCMD_parameter(p, 0, ¶mStart, FALSE, FALSE));
2716 if (!*operator) goto syntax_err;
2718 p += strlenW(operator);
2720 strcpyW(rightOperand, WCMD_parameter(p, 0, ¶mStart, TRUE, FALSE));
2724 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2728 p = paramStart + strlenW(rightOperand);
2729 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2732 /* Process rest of IF statement which is on the same line
2733 Note: This may process all or some of the cmdList (eg a GOTO) */
2734 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2738 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2741 /****************************************************************************
2744 * Move a file, directory tree or wildcarded set of files.
2747 void WCMD_move (void)
2750 WIN32_FIND_DATAW fd;
2752 WCHAR input[MAX_PATH];
2753 WCHAR output[MAX_PATH];
2755 WCHAR dir[MAX_PATH];
2756 WCHAR fname[MAX_PATH];
2757 WCHAR ext[MAX_PATH];
2759 if (param1[0] == 0x00) {
2760 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2764 /* If no destination supplied, assume current directory */
2765 if (param2[0] == 0x00) {
2766 strcpyW(param2, dotW);
2769 /* If 2nd parm is directory, then use original filename */
2770 /* Convert partial path to full path */
2771 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2772 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2773 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2774 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2776 /* Split into components */
2777 WCMD_splitpath(input, drive, dir, fname, ext);
2779 hff = FindFirstFileW(input, &fd);
2780 if (hff == INVALID_HANDLE_VALUE)
2784 WCHAR dest[MAX_PATH];
2785 WCHAR src[MAX_PATH];
2789 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2791 /* Build src & dest name */
2792 strcpyW(src, drive);
2795 /* See if dest is an existing directory */
2796 attribs = GetFileAttributesW(output);
2797 if (attribs != INVALID_FILE_ATTRIBUTES &&
2798 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2799 strcpyW(dest, output);
2800 strcatW(dest, slashW);
2801 strcatW(dest, fd.cFileName);
2803 strcpyW(dest, output);
2806 strcatW(src, fd.cFileName);
2808 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2809 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2811 /* If destination exists, prompt unless /Y supplied */
2812 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2814 WCHAR copycmd[MAXSTRING];
2817 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2818 if (strstrW (quals, parmNoY))
2820 else if (strstrW (quals, parmY))
2823 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2824 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2825 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2826 && ! lstrcmpiW (copycmd, parmY));
2829 /* Prompt if overwriting */
2833 /* Ask for confirmation */
2834 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2835 ok = WCMD_ask_confirm(question, FALSE, NULL);
2836 LocalFree(question);
2838 /* So delete the destination prior to the move */
2840 if (!DeleteFileW(dest)) {
2841 WCMD_print_error ();
2850 status = MoveFileW(src, dest);
2852 status = 1; /* Anything other than 0 to prevent error msg below */
2856 WCMD_print_error ();
2859 } while (FindNextFileW(hff, &fd) != 0);
2864 /****************************************************************************
2867 * Suspend execution of a batch script until a key is typed
2870 void WCMD_pause (void)
2876 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2878 have_console = GetConsoleMode(hIn, &oldmode);
2880 SetConsoleMode(hIn, 0);
2882 WCMD_output_asis(anykey);
2883 WCMD_ReadFile(hIn, &key, 1, &count);
2885 SetConsoleMode(hIn, oldmode);
2888 /****************************************************************************
2891 * Delete a directory.
2894 void WCMD_remove_dir (WCHAR *args) {
2897 int argsProcessed = 0;
2899 static const WCHAR parmS[] = {'/','S','\0'};
2900 static const WCHAR parmQ[] = {'/','Q','\0'};
2902 /* Loop through all args */
2904 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2905 if (argN && argN[0] != '/') {
2906 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2907 wine_dbgstr_w(quals));
2910 /* If subdirectory search not supplied, just try to remove
2911 and report error if it fails (eg if it contains a file) */
2912 if (strstrW (quals, parmS) == NULL) {
2913 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2915 /* Otherwise use ShFileOp to recursively remove a directory */
2918 SHFILEOPSTRUCTW lpDir;
2921 if (strstrW (quals, parmQ) == NULL) {
2923 WCHAR question[MAXSTRING];
2924 static const WCHAR fmt[] = {'%','s',' ','\0'};
2926 /* Ask for confirmation */
2927 wsprintfW(question, fmt, thisArg);
2928 ok = WCMD_ask_confirm(question, TRUE, NULL);
2930 /* Abort if answer is 'N' */
2937 lpDir.pFrom = thisArg;
2938 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2939 lpDir.wFunc = FO_DELETE;
2941 /* SHFileOperationW needs file list with a double null termination */
2942 thisArg[lstrlenW(thisArg) + 1] = 0x00;
2944 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
2949 /* Handle no valid args */
2950 if (argsProcessed == 0) {
2951 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2957 /****************************************************************************
2963 void WCMD_rename (void)
2967 WIN32_FIND_DATAW fd;
2968 WCHAR input[MAX_PATH];
2969 WCHAR *dotDst = NULL;
2971 WCHAR dir[MAX_PATH];
2972 WCHAR fname[MAX_PATH];
2973 WCHAR ext[MAX_PATH];
2977 /* Must be at least two args */
2978 if (param1[0] == 0x00 || param2[0] == 0x00) {
2979 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2984 /* Destination cannot contain a drive letter or directory separator */
2985 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
2986 SetLastError(ERROR_INVALID_PARAMETER);
2992 /* Convert partial path to full path */
2993 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2994 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2995 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
2996 dotDst = strchrW(param2, '.');
2998 /* Split into components */
2999 WCMD_splitpath(input, drive, dir, fname, ext);
3001 hff = FindFirstFileW(input, &fd);
3002 if (hff == INVALID_HANDLE_VALUE)
3006 WCHAR dest[MAX_PATH];
3007 WCHAR src[MAX_PATH];
3008 WCHAR *dotSrc = NULL;
3011 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3013 /* FIXME: If dest name or extension is *, replace with filename/ext
3014 part otherwise use supplied name. This supports:
3016 ren jim.* fred.* etc
3017 However, windows has a more complex algorithm supporting eg
3018 ?'s and *'s mid name */
3019 dotSrc = strchrW(fd.cFileName, '.');
3021 /* Build src & dest name */
3022 strcpyW(src, drive);
3025 dirLen = strlenW(src);
3026 strcatW(src, fd.cFileName);
3029 if (param2[0] == '*') {
3030 strcatW(dest, fd.cFileName);
3031 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3033 strcatW(dest, param2);
3034 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3037 /* Build Extension */
3038 if (dotDst && (*(dotDst+1)=='*')) {
3039 if (dotSrc) strcatW(dest, dotSrc);
3040 } else if (dotDst) {
3041 if (dotDst) strcatW(dest, dotDst);
3044 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3045 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3047 status = MoveFileW(src, dest);
3050 WCMD_print_error ();
3053 } while (FindNextFileW(hff, &fd) != 0);
3058 /*****************************************************************************
3061 * Make a copy of the environment.
3063 static WCHAR *WCMD_dupenv( const WCHAR *env )
3073 len += (strlenW(&env[len]) + 1);
3075 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3078 WINE_ERR("out of memory\n");
3081 memcpy (env_copy, env, len*sizeof (WCHAR));
3087 /*****************************************************************************
3090 * setlocal pushes the environment onto a stack
3091 * Save the environment as unicode so we don't screw anything up.
3093 void WCMD_setlocal (const WCHAR *s) {
3095 struct env_stack *env_copy;
3096 WCHAR cwd[MAX_PATH];
3098 /* setlocal does nothing outside of batch programs */
3099 if (!context) return;
3101 /* DISABLEEXTENSIONS ignored */
3103 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3106 WINE_ERR ("out of memory\n");
3110 env = GetEnvironmentStringsW ();
3111 env_copy->strings = WCMD_dupenv (env);
3112 if (env_copy->strings)
3114 env_copy->batchhandle = context->h;
3115 env_copy->next = saved_environment;
3116 saved_environment = env_copy;
3118 /* Save the current drive letter */
3119 GetCurrentDirectoryW(MAX_PATH, cwd);
3120 env_copy->u.cwd = cwd[0];
3123 LocalFree (env_copy);
3125 FreeEnvironmentStringsW (env);
3129 /*****************************************************************************
3132 * endlocal pops the environment off a stack
3133 * Note: When searching for '=', search from WCHAR position 1, to handle
3134 * special internal environment variables =C:, =D: etc
3136 void WCMD_endlocal (void) {
3137 WCHAR *env, *old, *p;
3138 struct env_stack *temp;
3141 /* setlocal does nothing outside of batch programs */
3142 if (!context) return;
3144 /* setlocal needs a saved environment from within the same context (batch
3145 program) as it was saved in */
3146 if (!saved_environment || saved_environment->batchhandle != context->h)
3149 /* pop the old environment from the stack */
3150 temp = saved_environment;
3151 saved_environment = temp->next;
3153 /* delete the current environment, totally */
3154 env = GetEnvironmentStringsW ();
3155 old = WCMD_dupenv (env);
3158 n = strlenW(&old[len]) + 1;
3159 p = strchrW(&old[len] + 1, '=');
3163 SetEnvironmentVariableW (&old[len], NULL);
3168 FreeEnvironmentStringsW (env);
3170 /* restore old environment */
3171 env = temp->strings;
3174 n = strlenW(&env[len]) + 1;
3175 p = strchrW(&env[len] + 1, '=');
3179 SetEnvironmentVariableW (&env[len], p);
3184 /* Restore current drive letter */
3185 if (IsCharAlphaW(temp->u.cwd)) {
3187 WCHAR cwd[MAX_PATH];
3188 static const WCHAR fmt[] = {'=','%','c',':','\0'};
3190 wsprintfW(envvar, fmt, temp->u.cwd);
3191 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3192 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3193 SetCurrentDirectoryW(cwd);
3201 /*****************************************************************************
3202 * WCMD_setshow_default
3204 * Set/Show the current default directory
3207 void WCMD_setshow_default (const WCHAR *args) {
3213 WIN32_FIND_DATAW fd;
3215 static const WCHAR parmD[] = {'/','D','\0'};
3217 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3219 /* Skip /D and trailing whitespace if on the front of the command line */
3220 if (CompareStringW(LOCALE_USER_DEFAULT,
3221 NORM_IGNORECASE | SORT_STRINGSORT,
3222 args, 2, parmD, -1) == CSTR_EQUAL) {
3224 while (*args && (*args==' ' || *args=='\t'))
3228 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3229 if (strlenW(args) == 0) {
3230 strcatW (cwd, newlineW);
3231 WCMD_output_asis (cwd);
3234 /* Remove any double quotes, which may be in the
3235 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3238 if (*args != '"') *pos++ = *args;
3241 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3245 /* Search for appropriate directory */
3246 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3247 hff = FindFirstFileW(string, &fd);
3248 if (hff != INVALID_HANDLE_VALUE) {
3250 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3251 WCHAR fpath[MAX_PATH];
3253 WCHAR dir[MAX_PATH];
3254 WCHAR fname[MAX_PATH];
3255 WCHAR ext[MAX_PATH];
3256 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3258 /* Convert path into actual directory spec */
3259 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3260 WCMD_splitpath(fpath, drive, dir, fname, ext);
3263 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3266 } while (FindNextFileW(hff, &fd) != 0);
3270 /* Change to that directory */
3271 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3273 status = SetCurrentDirectoryW(string);
3276 WCMD_print_error ();
3280 /* Save away the actual new directory, to store as current location */
3281 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3283 /* Restore old directory if drive letter would change, and
3284 CD x:\directory /D (or pushd c:\directory) not supplied */
3285 if ((strstrW(quals, parmD) == NULL) &&
3286 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3287 SetCurrentDirectoryW(cwd);
3291 /* Set special =C: type environment variable, for drive letter of
3292 change of directory, even if path was restored due to missing
3293 /D (allows changing drive letter when not resident on that
3295 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3297 strcpyW(env, equalW);
3298 memcpy(env+1, string, 2 * sizeof(WCHAR));
3300 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3301 SetEnvironmentVariableW(env, string);
3308 /****************************************************************************
3311 * Set/Show the system date
3312 * FIXME: Can't change date yet
3315 void WCMD_setshow_date (void) {
3317 WCHAR curdate[64], buffer[64];
3319 static const WCHAR parmT[] = {'/','T','\0'};
3321 if (strlenW(param1) == 0) {
3322 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3323 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3324 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3325 if (strstrW (quals, parmT) == NULL) {
3326 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3327 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3329 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3333 else WCMD_print_error ();
3336 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3340 /****************************************************************************
3342 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3345 static int WCMD_compare( const void *a, const void *b )
3348 const WCHAR * const *str_a = a, * const *str_b = b;
3349 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3350 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3351 if( r == CSTR_LESS_THAN ) return -1;
3352 if( r == CSTR_GREATER_THAN ) return 1;
3356 /****************************************************************************
3357 * WCMD_setshow_sortenv
3359 * sort variables into order for display
3360 * Optionally only display those who start with a stub
3361 * returns the count displayed
3363 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3365 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3368 if (stub) stublen = strlenW(stub);
3370 /* count the number of strings, and the total length */
3372 len += (strlenW(&s[len]) + 1);
3376 /* add the strings to an array */
3377 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3381 for( i=1; i<count; i++ )
3382 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3384 /* sort the array */
3385 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3388 for( i=0; i<count; i++ ) {
3389 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3390 NORM_IGNORECASE | SORT_STRINGSORT,
3391 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3392 /* Don't display special internal variables */
3393 if (str[i][0] != '=') {
3394 WCMD_output_asis(str[i]);
3395 WCMD_output_asis(newlineW);
3402 return displayedcount;
3405 /****************************************************************************
3408 * Set/Show the environment variables
3411 void WCMD_setshow_env (WCHAR *s) {
3416 static const WCHAR parmP[] = {'/','P','\0'};
3418 if (param1[0] == 0x00 && quals[0] == 0x00) {
3419 env = GetEnvironmentStringsW();
3420 WCMD_setshow_sortenv( env, NULL );
3424 /* See if /P supplied, and if so echo the prompt, and read in a reply */
3425 if (CompareStringW(LOCALE_USER_DEFAULT,
3426 NORM_IGNORECASE | SORT_STRINGSORT,
3427 s, 2, parmP, -1) == CSTR_EQUAL) {
3428 WCHAR string[MAXSTRING];
3432 while (*s && (*s==' ' || *s=='\t')) s++;
3434 WCMD_strip_quotes(s);
3436 /* If no parameter, or no '=' sign, return an error */
3437 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
3438 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3442 /* Output the prompt */
3444 if (strlenW(p) != 0) WCMD_output_asis(p);
3446 /* Read the reply */
3447 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3449 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3450 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3451 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3452 wine_dbgstr_w(string));
3453 status = SetEnvironmentVariableW(s, string);
3460 WCMD_strip_quotes(s);
3461 p = strchrW (s, '=');
3463 env = GetEnvironmentStringsW();
3464 if (WCMD_setshow_sortenv( env, s ) == 0) {
3465 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
3472 if (strlenW(p) == 0) p = NULL;
3473 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3475 status = SetEnvironmentVariableW(s, p);
3476 gle = GetLastError();
3477 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
3479 } else if ((!status)) WCMD_print_error();
3480 else errorlevel = 0;
3484 /****************************************************************************
3487 * Set/Show the path environment variable
3490 void WCMD_setshow_path (const WCHAR *args) {
3494 static const WCHAR pathW[] = {'P','A','T','H','\0'};
3495 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
3497 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
3498 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
3500 WCMD_output_asis ( pathEqW);
3501 WCMD_output_asis ( string);
3502 WCMD_output_asis ( newlineW);
3505 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
3509 if (*args == '=') args++; /* Skip leading '=' */
3510 status = SetEnvironmentVariableW(pathW, args);
3511 if (!status) WCMD_print_error();
3515 /****************************************************************************
3516 * WCMD_setshow_prompt
3518 * Set or show the command prompt.
3521 void WCMD_setshow_prompt (void) {
3524 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
3526 if (strlenW(param1) == 0) {
3527 SetEnvironmentVariableW(promptW, NULL);
3531 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
3532 if (strlenW(s) == 0) {
3533 SetEnvironmentVariableW(promptW, NULL);
3535 else SetEnvironmentVariableW(promptW, s);
3539 /****************************************************************************
3542 * Set/Show the system time
3543 * FIXME: Can't change time yet
3546 void WCMD_setshow_time (void) {
3548 WCHAR curtime[64], buffer[64];
3551 static const WCHAR parmT[] = {'/','T','\0'};
3553 if (strlenW(param1) == 0) {
3555 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
3556 curtime, sizeof(curtime)/sizeof(WCHAR))) {
3557 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
3558 if (strstrW (quals, parmT) == NULL) {
3559 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
3560 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3562 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3566 else WCMD_print_error ();
3569 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3573 /****************************************************************************
3576 * Shift batch parameters.
3577 * Optional /n says where to start shifting (n=0-8)
3580 void WCMD_shift (const WCHAR *args) {
3583 if (context != NULL) {
3584 WCHAR *pos = strchrW(args, '/');
3589 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
3590 start = (*(pos+1) - '0');
3592 SetLastError(ERROR_INVALID_PARAMETER);
3597 WINE_TRACE("Shifting variables, starting at %d\n", start);
3598 for (i=start;i<=8;i++) {
3599 context -> shift_count[i] = context -> shift_count[i+1] + 1;
3601 context -> shift_count[9] = context -> shift_count[9] + 1;
3606 /****************************************************************************
3609 void WCMD_start(const WCHAR *args)
3611 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
3612 '\\','s','t','a','r','t','.','e','x','e',0};
3613 WCHAR file[MAX_PATH];
3616 PROCESS_INFORMATION pi;
3618 GetWindowsDirectoryW( file, MAX_PATH );
3619 strcatW( file, exeW );
3620 cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
3621 strcpyW( cmdline, file );
3622 strcatW( cmdline, spaceW );
3623 strcatW( cmdline, args );
3625 memset( &st, 0, sizeof(STARTUPINFOW) );
3626 st.cb = sizeof(STARTUPINFOW);
3628 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
3630 WaitForSingleObject( pi.hProcess, INFINITE );
3631 GetExitCodeProcess( pi.hProcess, &errorlevel );
3632 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
3633 CloseHandle(pi.hProcess);
3634 CloseHandle(pi.hThread);
3638 SetLastError(ERROR_FILE_NOT_FOUND);
3639 WCMD_print_error ();
3645 /****************************************************************************
3648 * Set the console title
3650 void WCMD_title (const WCHAR *args) {
3651 SetConsoleTitleW(args);
3654 /****************************************************************************
3657 * Copy a file to standard output.
3660 void WCMD_type (WCHAR *args) {
3664 BOOL writeHeaders = FALSE;
3666 if (param1[0] == 0x00) {
3667 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3671 if (param2[0] != 0x00) writeHeaders = TRUE;
3673 /* Loop through all args */
3676 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3684 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3685 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3686 FILE_ATTRIBUTE_NORMAL, NULL);
3687 if (h == INVALID_HANDLE_VALUE) {
3688 WCMD_print_error ();
3689 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3693 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
3694 WCMD_output(fmt, thisArg);
3696 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
3697 if (count == 0) break; /* ReadFile reports success on EOF! */
3699 WCMD_output_asis (buffer);
3706 /****************************************************************************
3709 * Output either a file or stdin to screen in pages
3712 void WCMD_more (WCHAR *args) {
3717 WCHAR moreStrPage[100];
3720 static const WCHAR moreStart[] = {'-','-',' ','\0'};
3721 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
3722 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
3723 ')',' ','-','-','\n','\0'};
3724 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
3726 /* Prefix the NLS more with '-- ', then load the text */
3728 strcpyW(moreStr, moreStart);
3729 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
3730 (sizeof(moreStr)/sizeof(WCHAR))-3);
3732 if (param1[0] == 0x00) {
3734 /* Wine implements pipes via temporary files, and hence stdin is
3735 effectively reading from the file. This means the prompts for
3736 more are satisfied by the next line from the input (file). To
3737 avoid this, ensure stdin is to the console */
3738 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
3739 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
3740 FILE_SHARE_READ, NULL, OPEN_EXISTING,
3741 FILE_ATTRIBUTE_NORMAL, 0);
3742 WINE_TRACE("No parms - working probably in pipe mode\n");
3743 SetStdHandle(STD_INPUT_HANDLE, hConIn);
3745 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
3746 once you get in this bit unless due to a pipe, its going to end badly... */
3747 wsprintfW(moreStrPage, moreFmt, moreStr);
3749 WCMD_enter_paged_mode(moreStrPage);
3750 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3751 if (count == 0) break; /* ReadFile reports success on EOF! */
3753 WCMD_output_asis (buffer);
3755 WCMD_leave_paged_mode();
3757 /* Restore stdin to what it was */
3758 SetStdHandle(STD_INPUT_HANDLE, hstdin);
3759 CloseHandle(hConIn);
3763 BOOL needsPause = FALSE;
3765 /* Loop through all args */
3766 WINE_TRACE("Parms supplied - working through each file\n");
3767 WCMD_enter_paged_mode(moreStrPage);
3770 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3778 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
3779 WCMD_leave_paged_mode();
3780 WCMD_output_asis(moreStrPage);
3781 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3782 WCMD_enter_paged_mode(moreStrPage);
3786 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3787 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3788 FILE_ATTRIBUTE_NORMAL, NULL);
3789 if (h == INVALID_HANDLE_VALUE) {
3790 WCMD_print_error ();
3791 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3795 ULONG64 fileLen = 0;
3796 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
3798 /* Get the file size */
3799 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
3800 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
3803 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3804 if (count == 0) break; /* ReadFile reports success on EOF! */
3808 /* Update % count (would be used in WCMD_output_asis as prompt) */
3809 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
3811 WCMD_output_asis (buffer);
3817 WCMD_leave_paged_mode();
3821 /****************************************************************************
3824 * Display verify flag.
3825 * FIXME: We don't actually do anything with the verify flag other than toggle
3829 void WCMD_verify (const WCHAR *args) {
3833 count = strlenW(args);
3835 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
3836 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
3839 if (lstrcmpiW(args, onW) == 0) {
3843 else if (lstrcmpiW(args, offW) == 0) {
3844 verify_mode = FALSE;
3847 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
3850 /****************************************************************************
3853 * Display version info.
3856 void WCMD_version (void) {
3858 WCMD_output_asis (version_string);
3862 /****************************************************************************
3865 * Display volume information (set_label = FALSE)
3866 * Additionally set volume label (set_label = TRUE)
3867 * Returns 1 on success, 0 otherwise
3870 int WCMD_volume(BOOL set_label, const WCHAR *path)
3872 DWORD count, serial;
3873 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
3876 if (strlenW(path) == 0) {
3877 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
3879 WCMD_print_error ();
3882 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
3883 &serial, NULL, NULL, NULL, 0);
3886 static const WCHAR fmt[] = {'%','s','\\','\0'};
3887 if ((path[1] != ':') || (strlenW(path) != 2)) {
3888 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
3891 wsprintfW (curdir, fmt, path);
3892 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
3897 WCMD_print_error ();
3900 if (label[0] != '\0') {
3901 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
3905 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
3908 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
3909 HIWORD(serial), LOWORD(serial));
3911 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
3912 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3914 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3915 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3917 if (strlenW(path) != 0) {
3918 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
3921 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
3927 /**************************************************************************
3930 * Exit either the process, or just this batch program
3934 void WCMD_exit (CMD_LIST **cmdList) {
3936 static const WCHAR parmB[] = {'/','B','\0'};
3937 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
3939 if (context && lstrcmpiW(quals, parmB) == 0) {
3941 context -> skip_rest = TRUE;
3949 /*****************************************************************************
3952 * Lists or sets file associations (assoc = TRUE)
3953 * Lists or sets file types (assoc = FALSE)
3955 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
3958 DWORD accessOptions = KEY_READ;
3960 LONG rc = ERROR_SUCCESS;
3961 WCHAR keyValue[MAXSTRING];
3962 DWORD valueLen = MAXSTRING;
3964 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
3965 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
3967 /* See if parameter includes '=' */
3969 newValue = strchrW(args, '=');
3970 if (newValue) accessOptions |= KEY_WRITE;
3972 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
3973 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
3974 accessOptions, &key) != ERROR_SUCCESS) {
3975 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
3979 /* If no parameters then list all associations */
3980 if (*args == 0x00) {
3983 /* Enumerate all the keys */
3984 while (rc != ERROR_NO_MORE_ITEMS) {
3985 WCHAR keyName[MAXSTRING];
3988 /* Find the next value */
3989 nameLen = MAXSTRING;
3990 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
3992 if (rc == ERROR_SUCCESS) {
3994 /* Only interested in extension ones if assoc, or others
3996 if ((keyName[0] == '.' && assoc) ||
3997 (!(keyName[0] == '.') && (!assoc)))
3999 WCHAR subkey[MAXSTRING];
4000 strcpyW(subkey, keyName);
4001 if (!assoc) strcatW(subkey, shOpCmdW);
4003 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4005 valueLen = sizeof(keyValue)/sizeof(WCHAR);
4006 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4007 WCMD_output_asis(keyName);
4008 WCMD_output_asis(equalW);
4009 /* If no default value found, leave line empty after '=' */
4010 if (rc == ERROR_SUCCESS) {
4011 WCMD_output_asis(keyValue);
4013 WCMD_output_asis(newlineW);
4014 RegCloseKey(readKey);
4022 /* Parameter supplied - if no '=' on command line, its a query */
4023 if (newValue == NULL) {
4025 WCHAR subkey[MAXSTRING];
4027 /* Query terminates the parameter at the first space */
4028 strcpyW(keyValue, args);
4029 space = strchrW(keyValue, ' ');
4030 if (space) *space=0x00;
4032 /* Set up key name */
4033 strcpyW(subkey, keyValue);
4034 if (!assoc) strcatW(subkey, shOpCmdW);
4036 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4038 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4039 WCMD_output_asis(args);
4040 WCMD_output_asis(equalW);
4041 /* If no default value found, leave line empty after '=' */
4042 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4043 WCMD_output_asis(newlineW);
4044 RegCloseKey(readKey);
4047 WCHAR msgbuffer[MAXSTRING];
4049 /* Load the translated 'File association not found' */
4051 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4053 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4055 WCMD_output_stderr(msgbuffer, keyValue);
4059 /* Not a query - its a set or clear of a value */
4062 WCHAR subkey[MAXSTRING];
4064 /* Get pointer to new value */
4068 /* Set up key name */
4069 strcpyW(subkey, args);
4070 if (!assoc) strcatW(subkey, shOpCmdW);
4072 /* If nothing after '=' then clear value - only valid for ASSOC */
4073 if (*newValue == 0x00) {
4075 if (assoc) rc = RegDeleteKeyW(key, args);
4076 if (assoc && rc == ERROR_SUCCESS) {
4077 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4079 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4084 WCHAR msgbuffer[MAXSTRING];
4086 /* Load the translated 'File association not found' */
4088 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
4089 sizeof(msgbuffer)/sizeof(WCHAR));
4091 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
4092 sizeof(msgbuffer)/sizeof(WCHAR));
4094 WCMD_output_stderr(msgbuffer, keyValue);
4098 /* It really is a set value = contents */
4100 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4101 accessOptions, NULL, &readKey, NULL);
4102 if (rc == ERROR_SUCCESS) {
4103 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4105 sizeof(WCHAR) * (strlenW(newValue) + 1));
4106 RegCloseKey(readKey);
4109 if (rc != ERROR_SUCCESS) {
4113 WCMD_output_asis(args);
4114 WCMD_output_asis(equalW);
4115 WCMD_output_asis(newValue);
4116 WCMD_output_asis(newlineW);
4126 /****************************************************************************
4129 * Colors the terminal screen.
4132 void WCMD_color (void) {
4134 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4135 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4137 if (param1[0] != 0x00 && strlenW(param1) > 2) {
4138 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4142 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4148 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4153 /* Convert the color hex digits */
4154 if (param1[0] == 0x00) {
4155 color = defaultColor;
4157 color = strtoulW(param1, NULL, 16);
4160 /* Fail if fg == bg color */
4161 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4166 /* Set the current screen contents and ensure all future writes
4167 remain this color */
4168 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4169 SetConsoleTextAttribute(hStdOut, color);