2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
37 extern int defaultColor;
38 extern BOOL echo_mode;
39 extern BOOL interactive;
41 struct env_stack *pushd_directories;
42 const WCHAR dotW[] = {'.','\0'};
43 const WCHAR dotdotW[] = {'.','.','\0'};
44 const WCHAR nullW[] = {'\0'};
45 const WCHAR starW[] = {'*','\0'};
46 const WCHAR slashW[] = {'\\','\0'};
47 const WCHAR equalW[] = {'=','\0'};
48 const WCHAR wildcardsW[] = {'*','?','\0'};
49 const WCHAR slashstarW[] = {'\\','*','\0'};
50 const WCHAR inbuilt[][10] = {
51 {'C','A','L','L','\0'},
53 {'C','H','D','I','R','\0'},
55 {'C','O','P','Y','\0'},
56 {'C','T','T','Y','\0'},
57 {'D','A','T','E','\0'},
60 {'E','C','H','O','\0'},
61 {'E','R','A','S','E','\0'},
63 {'G','O','T','O','\0'},
64 {'H','E','L','P','\0'},
66 {'L','A','B','E','L','\0'},
68 {'M','K','D','I','R','\0'},
69 {'M','O','V','E','\0'},
70 {'P','A','T','H','\0'},
71 {'P','A','U','S','E','\0'},
72 {'P','R','O','M','P','T','\0'},
75 {'R','E','N','A','M','E','\0'},
77 {'R','M','D','I','R','\0'},
79 {'S','H','I','F','T','\0'},
80 {'S','T','A','R','T','\0'},
81 {'T','I','M','E','\0'},
82 {'T','I','T','L','E','\0'},
83 {'T','Y','P','E','\0'},
84 {'V','E','R','I','F','Y','\0'},
87 {'E','N','D','L','O','C','A','L','\0'},
88 {'S','E','T','L','O','C','A','L','\0'},
89 {'P','U','S','H','D','\0'},
90 {'P','O','P','D','\0'},
91 {'A','S','S','O','C','\0'},
92 {'C','O','L','O','R','\0'},
93 {'F','T','Y','P','E','\0'},
94 {'M','O','R','E','\0'},
95 {'C','H','O','I','C','E','\0'},
96 {'E','X','I','T','\0'}
98 static const WCHAR externals[][10] = {
99 {'A','T','T','R','I','B','\0'},
100 {'X','C','O','P','Y','\0'}
102 static const WCHAR fslashW[] = {'/','\0'};
103 static const WCHAR onW[] = {'O','N','\0'};
104 static const WCHAR offW[] = {'O','F','F','\0'};
105 static const WCHAR parmY[] = {'/','Y','\0'};
106 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
108 static HINSTANCE hinst;
109 struct env_stack *saved_environment;
110 static BOOL verify_mode = FALSE;
112 /**************************************************************************
115 * Issue a message and ask for confirmation, waiting on a valid answer.
117 * Returns True if Y (or A) answer is selected
118 * If optionAll contains a pointer, ALL is allowed, and if answered
122 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
126 WCHAR confirm[MAXSTRING];
127 WCHAR options[MAXSTRING];
128 WCHAR Ybuffer[MAXSTRING];
129 WCHAR Nbuffer[MAXSTRING];
130 WCHAR Abuffer[MAXSTRING];
131 WCHAR answer[MAX_PATH] = {'\0'};
134 /* Load the translated valid answers */
136 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
137 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
138 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
139 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
140 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
141 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
143 /* Loop waiting on a valid answer */
148 WCMD_output_asis (message);
150 WCMD_output_asis (confirm);
151 WCMD_output_asis (options);
152 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
153 answer[0] = toupperW(answer[0]);
154 if (answer[0] == Ybuffer[0])
156 if (answer[0] == Nbuffer[0])
158 if (optionAll && answer[0] == Abuffer[0])
166 /****************************************************************************
169 * Clear the terminal screen.
172 void WCMD_clear_screen (void) {
174 /* Emulate by filling the screen from the top left to bottom right with
175 spaces, then moving the cursor to the top left afterwards */
176 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
177 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
179 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
184 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
188 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
189 SetConsoleCursorPosition(hStdOut, topLeft);
193 /****************************************************************************
196 * Change the default i/o device (ie redirect STDin/STDout).
199 void WCMD_change_tty (void) {
201 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
205 /****************************************************************************
210 void WCMD_choice (const WCHAR * args) {
212 static const WCHAR bellW[] = {7,0};
213 static const WCHAR commaW[] = {',',0};
214 static const WCHAR bracket_open[] = {'[',0};
215 static const WCHAR bracket_close[] = {']','?',0};
220 WCHAR *my_command = NULL;
221 WCHAR opt_default = 0;
222 DWORD opt_timeout = 0;
229 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
232 my_command = WCMD_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 HeapFree(GetProcessHeap(), 0, 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 HeapFree(GetProcessHeap(), 0, 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 HeapFree(GetProcessHeap(), 0, 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 HeapFree(GetProcessHeap(), 0, 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());
403 /****************************************************************************
407 * optionally reading only until EOF (ascii copy)
408 * optionally appending onto an existing file (append)
409 * Returns TRUE on success
411 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
415 DWORD bytesread, byteswritten;
417 WINE_TRACE("ASCII Copying %s to %s (append?%d)\n",
418 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
420 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
421 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
423 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
427 /* Open the output file, overwriting if not appending */
428 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
429 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
431 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
435 /* Move to end of destination if we are going to append to it */
437 SetFilePointer(out, 0, NULL, FILE_END);
440 /* Loop copying data from source to destination until EOF read */
444 char buffer[MAXSTRING];
446 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
449 /* Stop at first EOF */
451 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
452 if (ptr) bytesread = (ptr - buffer);
456 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
457 if (!ok || byteswritten != bytesread) {
458 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
459 wine_dbgstr_w(dstname), GetLastError());
463 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
464 wine_dbgstr_w(srcname), GetLastError());
466 } while (ok && bytesread > 0);
473 /****************************************************************************
476 * Copy a file or wildcarded set.
477 * For ascii/binary type copies, it gets complex:
478 * Syntax on command line is
479 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
480 * Where first /a or /b sets 'mode in operation' until another is found
481 * once another is found, it applies to the file preceding the /a or /b
482 * In addition each filename can contain wildcards
483 * To make matters worse, the + may be in the same parameter (i.e. no
484 * whitespace) or with whitespace separating it
486 * ASCII mode on read == read and stop at first EOF
487 * ASCII mode on write == append EOF to destination
488 * Binary == copy as-is
490 * Design of this is to build up a list of files which will be copied into a
491 * list, then work through the list file by file.
492 * If no destination is specified, it defaults to the name of the first file in
493 * the list, but the current directory.
497 void WCMD_copy(WCHAR * args) {
499 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
505 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
506 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
507 BOOL anyconcats = FALSE; /* Have we found any + options */
508 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
509 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
510 BOOL prompt; /* Prompt before overwriting */
511 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
512 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
516 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
518 typedef struct _COPY_FILES
520 struct _COPY_FILES *next;
525 COPY_FILES *sourcelist = NULL;
526 COPY_FILES *lastcopyentry = NULL;
527 COPY_FILES *destination = NULL;
528 COPY_FILES *thiscopy = NULL;
529 COPY_FILES *prevcopy = NULL;
531 /* Assume we were successful! */
534 /* If no args supplied at all, report an error */
535 if (param1[0] == 0x00) {
536 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
541 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
543 /* Walk through all args, building up a list of files to process */
544 thisparam = WCMD_parameter(args, argno++, &rawarg, NULL, TRUE, FALSE);
545 while (*(thisparam)) {
549 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
551 /* Handle switches */
552 if (*thisparam == '/') {
553 while (*thisparam == '/') {
555 if (toupperW(*thisparam) == 'D') {
557 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
558 } else if (toupperW(*thisparam) == 'Y') {
560 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
562 } else if (toupperW(*thisparam) == 'V') {
564 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
565 } else if (toupperW(*thisparam) == 'N') {
567 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
568 } else if (toupperW(*thisparam) == 'Z') {
570 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
571 } else if (toupperW(*thisparam) == 'A') {
572 if (binarymode != 0) {
574 WINE_TRACE("Subsequent files will be handled as ASCII\n");
575 if (destination != NULL) {
576 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
577 destination->binarycopy = binarymode;
578 } else if (lastcopyentry != NULL) {
579 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
580 lastcopyentry->binarycopy = binarymode;
583 } else if (toupperW(*thisparam) == 'B') {
584 if (binarymode != 1) {
586 WINE_TRACE("Subsequent files will be handled as binary\n");
587 if (destination != NULL) {
588 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
589 destination->binarycopy = binarymode;
590 } else if (lastcopyentry != NULL) {
591 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
592 lastcopyentry->binarycopy = binarymode;
596 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
601 /* This parameter was purely switches, get the next one */
602 thisparam = WCMD_parameter(args, argno++, &rawarg, NULL, TRUE, FALSE);
606 /* We have found something which is not a switch. If could be anything of the form
607 sourcefilename (which could be destination too)
608 + (when filename + filename syntex used)
609 sourcefilename+sourcefilename
611 +/b[tests show windows then ignores to end of parameter]
614 if (*thisparam=='+') {
615 if (lastcopyentry == NULL) {
616 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
620 concatnextfilename = TRUE;
624 /* Move to next thing to process */
626 if (*thisparam == 0x00)
627 thisparam = WCMD_parameter(args, argno++, &rawarg, NULL, TRUE, FALSE);
631 /* We have found something to process - build a COPY_FILE block to store it */
632 thiscopy = HeapAlloc(GetProcessHeap(),0,sizeof(COPY_FILES));
633 if (thiscopy == NULL) goto exitreturn;
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 = HeapAlloc(GetProcessHeap(),0,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, NULL, 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 = HeapAlloc(GetProcessHeap(),0,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 HeapFree(GetProcessHeap(), 0, destination->name);
785 destination->name = WCMD_strdupW(destname);
786 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
787 wine_dbgstr_w(destname), appendfirstsource);
789 /* Now we need to walk the set of sources, and process each name we come to.
790 If anyconcats is true, we are writing to one file, otherwise we are using
791 the source name each time.
792 If destination exists, prompt for overwrite the first time (if concatenating
793 we ask each time until yes is answered)
794 The first source file we come across must exist (when wildcards expanded)
795 and if concatenating with overwrite prompts, each source file must exist
796 until a yes is answered. */
798 thiscopy = sourcelist;
801 while (thiscopy != NULL) {
803 WCHAR srcpath[MAX_PATH];
807 /* If it was not explicit, we now know whether we are concatenating or not and
808 hence whether to copy as binary or ascii */
809 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
811 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
812 to where the filename portion begins (used for wildcart expansion. */
813 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
814 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
816 /* If parameter is a directory, ensure it ends in \* */
817 attributes = GetFileAttributesW(srcpath);
818 if (srcpath[strlenW(srcpath) - 1] == '\\') {
820 /* We need to know where the filename part starts, so append * and
821 recalculate the full resulting path */
822 strcatW(thiscopy->name, starW);
823 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
824 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
826 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
827 (attributes != INVALID_FILE_ATTRIBUTES) &&
828 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
830 /* We need to know where the filename part starts, so append \* and
831 recalculate the full resulting path */
832 strcatW(thiscopy->name, slashstarW);
833 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
834 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
837 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
838 wine_dbgstr_w(srcpath), anyconcats);
840 /* Loop through all source files */
841 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
842 hff = FindFirstFileW(srcpath, &fd);
843 if (hff != INVALID_HANDLE_VALUE) {
845 WCHAR outname[MAX_PATH];
848 /* Skip . and .., and directories */
849 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
850 WINE_TRACE("Skipping directories\n");
853 /* Build final destination name */
854 strcpyW(outname, destination->name);
855 if (destisdirectory || appendfirstsource) strcatW(outname, fd.cFileName);
857 /* Build source name */
858 strcpyW(filenamepart, fd.cFileName);
860 /* Do we just overwrite */
862 if (anyconcats && writtenoneconcat) {
866 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
867 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
868 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
869 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
871 /* Prompt before overwriting */
873 DWORD attributes = GetFileAttributesW(outname);
874 if (attributes != INVALID_FILE_ATTRIBUTES) {
876 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
877 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
880 else overwrite = TRUE;
883 /* If we needed tyo save away the first filename, do it */
884 if (appendfirstsource && overwrite) {
885 HeapFree(GetProcessHeap(), 0, destination->name);
886 destination->name = WCMD_strdupW(outname);
887 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
888 appendfirstsource = FALSE;
889 destisdirectory = FALSE;
892 /* Do the copy as appropriate */
894 if (anyconcats && writtenoneconcat) {
895 if (thiscopy->binarycopy) {
896 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
898 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
900 } else if (!thiscopy->binarycopy) {
901 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
903 status = CopyFileW(srcpath, outname, FALSE);
909 WINE_TRACE("Copied successfully\n");
910 if (anyconcats) writtenoneconcat = TRUE;
912 /* Append EOF if ascii destination and we are not going to add more onto the end
913 Note: Testing shows windows has an optimization whereas if you have a binary
914 copy of a file to a single destination (ie concatenation) then it does not add
915 the EOF, hence the check on the source copy type below. */
916 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
917 if (!WCMD_AppendEOF(outname)) {
925 } while (FindNextFileW(hff, &fd) != 0);
928 /* Error if the first file was not found */
929 if (!anyconcats || (anyconcats && !writtenoneconcat)) {
935 /* Step on to the next supplied source */
936 thiscopy = thiscopy -> next;
939 /* Append EOF if ascii destination and we were concatenating */
940 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
941 if (!WCMD_AppendEOF(destination->name)) {
947 /* Exit out of the routine, freeing any remaining allocated memory */
950 thiscopy = sourcelist;
951 while (thiscopy != NULL) {
953 /* Free up this block*/
954 thiscopy = thiscopy -> next;
955 HeapFree(GetProcessHeap(), 0, prevcopy->name);
956 HeapFree(GetProcessHeap(), 0, prevcopy);
959 /* Free up the destination memory */
961 HeapFree(GetProcessHeap(), 0, destination->name);
962 HeapFree(GetProcessHeap(), 0, destination);
968 /****************************************************************************
971 * Create a directory (and, if needed, any intermediate directories).
973 * Modifies its argument by replacing slashes temporarily with nulls.
976 static BOOL create_full_path(WCHAR* path)
980 /* don't mess with drive letter portion of path, if any */
985 /* Strip trailing slashes. */
986 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
989 /* Step through path, creating intermediate directories as needed. */
990 /* First component includes drive letter, if any. */
994 /* Skip to end of component */
995 while (*p == '\\') p++;
996 while (*p && *p != '\\') p++;
998 /* path is now the original full path */
999 return CreateDirectoryW(path, NULL);
1001 /* Truncate path, create intermediate directory, and restore path */
1003 rv = CreateDirectoryW(path, NULL);
1005 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1012 void WCMD_create_dir (WCHAR *args) {
1016 if (param1[0] == 0x00) {
1017 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1020 /* Loop through all args */
1022 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, NULL, FALSE, FALSE);
1024 if (!create_full_path(thisArg)) {
1025 WCMD_print_error ();
1031 /* Parse the /A options given by the user on the commandline
1032 * into a bitmask of wanted attributes (*wantSet),
1033 * and a bitmask of unwanted attributes (*wantClear).
1035 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1036 static const WCHAR parmA[] = {'/','A','\0'};
1039 /* both are strictly 'out' parameters */
1043 /* For each /A argument */
1044 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1045 /* Skip /A itself */
1048 /* Skip optional : */
1051 /* For each of the attribute specifier chars to this /A option */
1052 for (; *p != 0 && *p != '/'; p++) {
1053 BOOL negate = FALSE;
1061 /* Convert the attribute specifier to a bit in one of the masks */
1063 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1064 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1065 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1066 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1068 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1078 /* If filename part of parameter is * or *.*,
1079 * and neither /Q nor /P options were given,
1080 * prompt the user whether to proceed.
1081 * Returns FALSE if user says no, TRUE otherwise.
1082 * *pPrompted is set to TRUE if the user is prompted.
1083 * (If /P supplied, del will prompt for individual files later.)
1085 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1086 static const WCHAR parmP[] = {'/','P','\0'};
1087 static const WCHAR parmQ[] = {'/','Q','\0'};
1089 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1090 static const WCHAR anyExt[]= {'.','*','\0'};
1092 WCHAR dir[MAX_PATH];
1093 WCHAR fname[MAX_PATH];
1094 WCHAR ext[MAX_PATH];
1095 WCHAR fpath[MAX_PATH];
1097 /* Convert path into actual directory spec */
1098 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1099 WCMD_splitpath(fpath, drive, dir, fname, ext);
1101 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1102 if ((strcmpW(fname, starW) == 0) &&
1103 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1105 WCHAR question[MAXSTRING];
1106 static const WCHAR fmt[] = {'%','s',' ','\0'};
1108 /* Caller uses this to suppress "file not found" warning later */
1111 /* Ask for confirmation */
1112 wsprintfW(question, fmt, fpath);
1113 return WCMD_ask_confirm(question, TRUE, NULL);
1116 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1120 /* Helper function for WCMD_delete().
1121 * Deletes a single file, directory, or wildcard.
1122 * If /S was given, does it recursively.
1123 * Returns TRUE if a file was deleted.
1125 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1127 static const WCHAR parmP[] = {'/','P','\0'};
1128 static const WCHAR parmS[] = {'/','S','\0'};
1129 static const WCHAR parmF[] = {'/','F','\0'};
1131 DWORD unwanted_attrs;
1133 WCHAR argCopy[MAX_PATH];
1134 WIN32_FIND_DATAW fd;
1136 WCHAR fpath[MAX_PATH];
1138 BOOL handleParm = TRUE;
1140 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1142 strcpyW(argCopy, thisArg);
1143 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1144 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1146 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1147 /* Skip this arg if user declines to delete *.* */
1151 /* First, try to delete in the current directory */
1152 hff = FindFirstFileW(argCopy, &fd);
1153 if (hff == INVALID_HANDLE_VALUE) {
1159 /* Support del <dirname> by just deleting all files dirname\* */
1161 && (strchrW(argCopy,'*') == NULL)
1162 && (strchrW(argCopy,'?') == NULL)
1163 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1165 WCHAR modifiedParm[MAX_PATH];
1166 static const WCHAR slashStar[] = {'\\','*','\0'};
1168 strcpyW(modifiedParm, argCopy);
1169 strcatW(modifiedParm, slashStar);
1172 WCMD_delete_one(modifiedParm);
1174 } else if (handleParm) {
1176 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1177 strcpyW (fpath, argCopy);
1179 p = strrchrW (fpath, '\\');
1182 strcatW (fpath, fd.cFileName);
1184 else strcpyW (fpath, fd.cFileName);
1185 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1188 /* Handle attribute matching (/A) */
1189 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1190 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1192 /* /P means prompt for each file */
1193 if (ok && strstrW (quals, parmP) != NULL) {
1196 /* Ask for confirmation */
1197 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1198 ok = WCMD_ask_confirm(question, FALSE, NULL);
1199 LocalFree(question);
1202 /* Only proceed if ok to */
1205 /* If file is read only, and /A:r or /F supplied, delete it */
1206 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1207 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1208 strstrW (quals, parmF) != NULL)) {
1209 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1212 /* Now do the delete */
1213 if (!DeleteFileW(fpath)) WCMD_print_error ();
1217 } while (FindNextFileW(hff, &fd) != 0);
1221 /* Now recurse into all subdirectories handling the parameter in the same way */
1222 if (strstrW (quals, parmS) != NULL) {
1224 WCHAR thisDir[MAX_PATH];
1228 WCHAR dir[MAX_PATH];
1229 WCHAR fname[MAX_PATH];
1230 WCHAR ext[MAX_PATH];
1232 /* Convert path into actual directory spec */
1233 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1234 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1236 strcpyW(thisDir, drive);
1237 strcatW(thisDir, dir);
1238 cPos = strlenW(thisDir);
1240 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1242 /* Append '*' to the directory */
1243 thisDir[cPos] = '*';
1244 thisDir[cPos+1] = 0x00;
1246 hff = FindFirstFileW(thisDir, &fd);
1248 /* Remove residual '*' */
1249 thisDir[cPos] = 0x00;
1251 if (hff != INVALID_HANDLE_VALUE) {
1252 DIRECTORY_STACK *allDirs = NULL;
1253 DIRECTORY_STACK *lastEntry = NULL;
1256 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1257 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1258 (strcmpW(fd.cFileName, dotW) != 0)) {
1260 DIRECTORY_STACK *nextDir;
1261 WCHAR subParm[MAX_PATH];
1263 /* Work out search parameter in sub dir */
1264 strcpyW (subParm, thisDir);
1265 strcatW (subParm, fd.cFileName);
1266 strcatW (subParm, slashW);
1267 strcatW (subParm, fname);
1268 strcatW (subParm, ext);
1269 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1271 /* Allocate memory, add to list */
1272 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
1273 if (allDirs == NULL) allDirs = nextDir;
1274 if (lastEntry != NULL) lastEntry->next = nextDir;
1275 lastEntry = nextDir;
1276 nextDir->next = NULL;
1277 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
1278 (strlenW(subParm)+1) * sizeof(WCHAR));
1279 strcpyW(nextDir->dirName, subParm);
1281 } while (FindNextFileW(hff, &fd) != 0);
1284 /* Go through each subdir doing the delete */
1285 while (allDirs != NULL) {
1286 DIRECTORY_STACK *tempDir;
1288 tempDir = allDirs->next;
1289 found |= WCMD_delete_one (allDirs->dirName);
1291 HeapFree(GetProcessHeap(),0,allDirs->dirName);
1292 HeapFree(GetProcessHeap(),0,allDirs);
1301 /****************************************************************************
1304 * Delete a file or wildcarded set.
1307 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1308 * - Each set is a pattern, eg /ahr /as-r means
1309 * readonly+hidden OR nonreadonly system files
1310 * - The '-' applies to a single field, ie /a:-hr means read only
1314 BOOL WCMD_delete (WCHAR *args) {
1317 BOOL argsProcessed = FALSE;
1318 BOOL foundAny = FALSE;
1322 for (argno=0; ; argno++) {
1327 thisArg = WCMD_parameter (args, argno, &argN, NULL, FALSE, FALSE);
1329 break; /* no more parameters */
1331 continue; /* skip options */
1333 argsProcessed = TRUE;
1334 found = WCMD_delete_one(thisArg);
1337 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1342 /* Handle no valid args */
1344 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1352 * Returns a trimmed version of s with all leading and trailing whitespace removed
1356 static WCHAR *WCMD_strtrim(const WCHAR *s)
1358 DWORD len = strlenW(s);
1359 const WCHAR *start = s;
1362 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
1365 while (isspaceW(*start)) start++;
1367 const WCHAR *end = s + len - 1;
1368 while (end > start && isspaceW(*end)) end--;
1369 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1370 result[end - start + 1] = '\0';
1378 /****************************************************************************
1381 * Echo input to the screen (or not). We don't try to emulate the bugs
1382 * in DOS (try typing "ECHO ON AGAIN" for an example).
1385 void WCMD_echo (const WCHAR *args)
1388 const WCHAR *origcommand = args;
1391 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1392 || args[0]==':' || args[0]==';')
1395 trimmed = WCMD_strtrim(args);
1396 if (!trimmed) return;
1398 count = strlenW(trimmed);
1399 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1400 && origcommand[0]!=';') {
1401 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1402 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1406 if (lstrcmpiW(trimmed, onW) == 0)
1408 else if (lstrcmpiW(trimmed, offW) == 0)
1411 WCMD_output_asis (args);
1412 WCMD_output_asis (newlineW);
1414 HeapFree(GetProcessHeap(), 0, trimmed);
1417 /*****************************************************************************
1420 * Execute a command, and any && or bracketed follow on to the command. The
1421 * first command to be executed may not be at the front of the
1422 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1424 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1425 const WCHAR *variable, const WCHAR *value,
1426 BOOL isIF, BOOL executecmds)
1428 CMD_LIST *curPosition = *cmdList;
1429 int myDepth = (*cmdList)->bracketDepth;
1431 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1432 cmdList, wine_dbgstr_w(firstcmd),
1433 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1436 /* Skip leading whitespace between condition and the command */
1437 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1439 /* Process the first command, if there is one */
1440 if (executecmds && firstcmd && *firstcmd) {
1441 WCHAR *command = WCMD_strdupW(firstcmd);
1442 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList, FALSE);
1443 HeapFree(GetProcessHeap(), 0, command);
1447 /* If it didn't move the position, step to next command */
1448 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1450 /* Process any other parts of the command */
1452 BOOL processThese = executecmds;
1455 static const WCHAR ifElse[] = {'e','l','s','e'};
1457 /* execute all appropriate commands */
1458 curPosition = *cmdList;
1460 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1462 (*cmdList)->prevDelim,
1463 (*cmdList)->bracketDepth, myDepth);
1465 /* Execute any statements appended to the line */
1466 /* FIXME: Only if previous call worked for && or failed for || */
1467 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1468 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1469 if (processThese && (*cmdList)->command) {
1470 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1471 value, cmdList, FALSE);
1473 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1475 /* Execute any appended to the statement with (...) */
1476 } else if ((*cmdList)->bracketDepth > myDepth) {
1478 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value, FALSE);
1479 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1481 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1483 /* End of the command - does 'ELSE ' follow as the next command? */
1486 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1487 (*cmdList)->command)) {
1489 /* Swap between if and else processing */
1490 processThese = !processThese;
1492 /* Process the ELSE part */
1494 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1495 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1497 /* Skip leading whitespace between condition and the command */
1498 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1500 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList, FALSE);
1503 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1505 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1514 /**************************************************************************
1517 * Batch file loop processing.
1519 * On entry: cmdList contains the syntax up to the set
1520 * next cmdList and all in that bracket contain the set data
1521 * next cmdlist contains the DO cmd
1522 * following that is either brackets or && entries (as per if)
1526 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1528 WIN32_FIND_DATAW fd;
1531 static const WCHAR inW[] = {'i','n'};
1532 static const WCHAR doW[] = {'d','o'};
1533 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1537 WCHAR optionsRoot[MAX_PATH];
1538 DIRECTORY_STACK *dirsToWalk = NULL;
1540 BOOL expandDirs = FALSE;
1541 BOOL useNumbers = FALSE;
1542 BOOL doFileset = FALSE;
1543 BOOL doRecurse = FALSE;
1544 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
1545 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1547 CMD_LIST *thisCmdStart;
1548 int parameterNo = 0;
1550 /* Handle optional qualifiers (multiple are allowed) */
1551 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE, FALSE);
1554 while (thisArg && *thisArg == '/') {
1555 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
1557 switch (toupperW(*thisArg)) {
1558 case 'D': expandDirs = TRUE; break;
1559 case 'L': useNumbers = TRUE; break;
1561 /* Recursive is special case - /R can have an optional path following it */
1562 /* filenamesets are another special case - /F can have an optional options following it */
1566 /* When recursing directories, use current directory as the starting point unless
1567 subsequently overridden */
1568 doRecurse = (toupperW(*thisArg) == 'R');
1569 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
1571 doFileset = (toupperW(*thisArg) == 'F');
1573 /* Retrieve next parameter to see if is root/options (raw form required
1574 with for /f, or unquoted in for /r) */
1575 thisArg = WCMD_parameter(p, parameterNo, NULL, NULL, doFileset, FALSE);
1577 /* Next parm is either qualifier, path/options or variable -
1578 only care about it if it is the path/options */
1579 if (thisArg && *thisArg != '/' && *thisArg != '%') {
1581 strcpyW(optionsRoot, thisArg);
1583 static unsigned int once;
1584 if (!once++) WINE_FIXME("/F needs to handle options\n");
1590 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
1593 /* Step to next token */
1594 thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE, FALSE);
1597 /* Ensure line continues with variable */
1598 if (!*thisArg || *thisArg != '%') {
1599 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1603 /* Set up the list of directories to recurse if we are going to */
1605 /* Allocate memory, add to list */
1606 dirsToWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1607 dirsToWalk->next = NULL;
1608 dirsToWalk->dirName = HeapAlloc(GetProcessHeap(),0,
1609 (strlenW(optionsRoot) + 1) * sizeof(WCHAR));
1610 strcpyW(dirsToWalk->dirName, optionsRoot);
1611 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
1614 /* Variable should follow */
1615 strcpyW(variable, thisArg);
1616 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1618 /* Ensure line continues with IN */
1619 thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE, FALSE);
1621 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1622 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
1623 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
1624 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1628 /* Save away where the set of data starts and the variable */
1629 thisDepth = (*cmdList)->bracketDepth;
1630 *cmdList = (*cmdList)->nextcommand;
1631 setStart = (*cmdList);
1633 /* Skip until the close bracket */
1634 WINE_TRACE("Searching %p as the set\n", *cmdList);
1636 (*cmdList)->command != NULL &&
1637 (*cmdList)->bracketDepth > thisDepth) {
1638 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1639 *cmdList = (*cmdList)->nextcommand;
1642 /* Skip the close bracket, if there is one */
1643 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1645 /* Syntax error if missing close bracket, or nothing following it
1646 and once we have the complete set, we expect a DO */
1647 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1648 if ((*cmdList == NULL)
1649 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1651 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1657 /* Loop repeatedly per-directory we are potentially walking, when in for /r
1658 mode, or once for the rest of the time. */
1660 WCHAR fullitem[MAX_PATH];
1662 /* Save away the starting position for the commands (and offset for the
1664 cmdStart = *cmdList;
1665 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1668 /* If we are recursing directories (ie /R), add all sub directories now, then
1669 prefix the root when searching for the item */
1671 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1673 /* Build a generic search and add all directories on the list of directories
1675 strcpyW(fullitem, dirsToWalk->dirName);
1676 strcatW(fullitem, slashstarW);
1677 hff = FindFirstFileW(fullitem, &fd);
1678 if (hff != INVALID_HANDLE_VALUE) {
1680 WINE_TRACE("Looking for subdirectories\n");
1681 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1682 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1683 (strcmpW(fd.cFileName, dotW) != 0))
1685 /* Allocate memory, add to list */
1686 DIRECTORY_STACK *toWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1687 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1688 toWalk->next = remainingDirs->next;
1689 remainingDirs->next = toWalk;
1690 remainingDirs = toWalk;
1691 toWalk->dirName = HeapAlloc(GetProcessHeap(), 0,
1693 (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1694 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1695 strcatW(toWalk->dirName, slashW);
1696 strcatW(toWalk->dirName, fd.cFileName);
1697 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1698 toWalk, toWalk->next);
1700 } while (FindNextFileW(hff, &fd) != 0);
1701 WINE_TRACE("Finished adding all subdirectories\n");
1707 /* Loop through all set entries */
1709 thisSet->command != NULL &&
1710 thisSet->bracketDepth >= thisDepth) {
1712 /* Loop through all entries on the same line */
1716 WINE_TRACE("Processing for set %p\n", thisSet);
1718 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL, TRUE, FALSE))) {
1721 * If the parameter within the set has a wildcard then search for matching files
1722 * otherwise do a literal substitution.
1724 static const WCHAR wildcards[] = {'*','?','\0'};
1725 thisCmdStart = cmdStart;
1728 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1730 if (!useNumbers && !doFileset) {
1731 WCHAR fullitem[MAX_PATH];
1733 /* Now build the item to use / search for in the specified directory,
1734 as it is fully qualified in the /R case */
1736 strcpyW(fullitem, dirsToWalk->dirName);
1737 strcatW(fullitem, slashW);
1738 strcatW(fullitem, item);
1740 strcpyW(fullitem, item);
1743 if (strpbrkW (fullitem, wildcards)) {
1745 hff = FindFirstFileW(fullitem, &fd);
1746 if (hff != INVALID_HANDLE_VALUE) {
1748 BOOL isDirectory = FALSE;
1750 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1752 /* Handle as files or dirs appropriately, but ignore . and .. */
1753 if (isDirectory == expandDirs &&
1754 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1755 (strcmpW(fd.cFileName, dotW) != 0))
1757 thisCmdStart = cmdStart;
1758 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1761 strcpyW(fullitem, dirsToWalk->dirName);
1762 strcatW(fullitem, slashW);
1763 strcatW(fullitem, fd.cFileName);
1765 strcpyW(fullitem, fd.cFileName);
1768 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1769 fullitem, FALSE, TRUE);
1770 cmdEnd = thisCmdStart;
1772 } while (FindNextFileW(hff, &fd) != 0);
1777 WCMD_part_execute(&thisCmdStart, firstCmd, variable, fullitem, FALSE, TRUE);
1778 cmdEnd = thisCmdStart;
1781 } else if (useNumbers) {
1782 /* Convert the first 3 numbers to signed longs and save */
1783 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1784 /* else ignore them! */
1786 /* Filesets - either a list of files, or a command to run and parse the output */
1787 } else if (doFileset && *itemStart != '"') {
1790 WCHAR temp_file[MAX_PATH];
1792 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1793 wine_dbgstr_w(item));
1795 /* If backquote or single quote, we need to launch that command
1796 and parse the results - use a temporary file */
1797 if (*itemStart == '`' || *itemStart == '\'') {
1799 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1800 static const WCHAR redirOut[] = {'>','%','s','\0'};
1801 static const WCHAR cmdW[] = {'C','M','D','\0'};
1803 /* Remove trailing character */
1804 itemStart[strlenW(itemStart)-1] = 0x00;
1806 /* Get temp filename */
1807 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1808 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1810 /* Execute program and redirect output */
1811 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1812 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL, FALSE);
1814 /* Open the file, read line by line and process */
1815 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1816 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1819 /* Open the file, read line by line and process */
1820 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1821 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1824 /* Process the input file */
1825 if (input == INVALID_HANDLE_VALUE) {
1826 WCMD_print_error ();
1827 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1829 return; /* FOR loop aborts at first failure here */
1833 WCHAR buffer[MAXSTRING];
1834 WCHAR *where, *parm;
1836 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1838 /* Skip blank lines*/
1839 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE, FALSE);
1840 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1841 wine_dbgstr_w(buffer));
1844 /* FIXME: The following should be moved into its own routine and
1845 reused for the string literal parsing below */
1846 thisCmdStart = cmdStart;
1848 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1849 cmdEnd = thisCmdStart;
1855 CloseHandle (input);
1858 /* Delete the temporary file */
1859 if (*itemStart == '`' || *itemStart == '\'') {
1860 DeleteFileW(temp_file);
1863 /* Filesets - A string literal */
1864 } else if (doFileset && *itemStart == '"') {
1865 WCHAR buffer[MAXSTRING];
1866 WCHAR *where, *parm;
1868 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1869 strcpyW(buffer, item);
1870 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE, FALSE);
1871 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1872 wine_dbgstr_w(buffer));
1875 /* FIXME: The following should be moved into its own routine and
1876 reused for the string literal parsing below */
1877 thisCmdStart = cmdStart;
1879 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1880 cmdEnd = thisCmdStart;
1884 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1888 /* Move onto the next set line */
1889 thisSet = thisSet->nextcommand;
1892 /* If /L is provided, now run the for loop */
1895 static const WCHAR fmt[] = {'%','d','\0'};
1897 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1898 numbers[0], numbers[2], numbers[1]);
1900 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
1903 sprintfW(thisNum, fmt, i);
1904 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1906 thisCmdStart = cmdStart;
1908 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1910 cmdEnd = thisCmdStart;
1913 /* If we are walking directories, move on to any which remain */
1914 if (dirsToWalk != NULL) {
1915 DIRECTORY_STACK *nextDir = dirsToWalk->next;
1916 HeapFree(GetProcessHeap(), 0, dirsToWalk->dirName);
1917 HeapFree(GetProcessHeap(), 0, dirsToWalk);
1918 dirsToWalk = nextDir;
1919 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
1920 wine_dbgstr_w(dirsToWalk->dirName));
1921 else WINE_TRACE("Finished all directories.\n");
1924 } while (dirsToWalk != NULL);
1926 /* Now skip over the do part if we did not perform the for loop so far.
1927 We store in cmdEnd the next command after the do block, but we only
1928 know this if something was run. If it has not been, we need to calculate
1931 thisCmdStart = cmdStart;
1932 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
1933 WCMD_part_execute(&thisCmdStart, firstCmd, NULL, NULL, FALSE, FALSE);
1934 cmdEnd = thisCmdStart;
1937 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1938 all processing, OR it should be pointing to the end of && processing OR
1939 it should be pointing at the NULL end of bracket for the DO. The return
1940 value needs to be the NEXT command to execute, which it either is, or
1941 we need to step over the closing bracket */
1943 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1946 /**************************************************************************
1949 * Simple on-line help. Help text is stored in the resource file.
1952 void WCMD_give_help (const WCHAR *args)
1956 args = WCMD_skip_leading_spaces((WCHAR*) args);
1957 if (strlenW(args) == 0) {
1958 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1961 /* Display help message for builtin commands */
1962 for (i=0; i<=WCMD_EXIT; i++) {
1963 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1964 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1965 WCMD_output_asis (WCMD_LoadMessage(i));
1969 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1970 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1971 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1972 args, -1, externals[i], -1) == CSTR_EQUAL) {
1974 static const WCHAR helpW[] = {' ', '/','?','\0'};
1976 strcatW(cmd, helpW);
1977 WCMD_run_program(cmd, FALSE);
1981 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
1986 /****************************************************************************
1989 * Batch file jump instruction. Not the most efficient algorithm ;-)
1990 * Prints error message if the specified label cannot be found - the file pointer is
1991 * then at EOF, effectively stopping the batch file.
1992 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1995 void WCMD_goto (CMD_LIST **cmdList) {
1997 WCHAR string[MAX_PATH];
1998 WCHAR current[MAX_PATH];
2000 /* Do not process any more parts of a processed multipart or multilines command */
2001 if (cmdList) *cmdList = NULL;
2003 if (context != NULL) {
2004 WCHAR *paramStart = param1, *str;
2005 static const WCHAR eofW[] = {':','e','o','f','\0'};
2007 if (param1[0] == 0x00) {
2008 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2012 /* Handle special :EOF label */
2013 if (lstrcmpiW (eofW, param1) == 0) {
2014 context -> skip_rest = TRUE;
2018 /* Support goto :label as well as goto label */
2019 if (*paramStart == ':') paramStart++;
2021 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2022 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2024 while (isspaceW (*str)) str++;
2028 while (((current[index] = str[index])) && (!isspaceW (current[index])))
2031 /* ignore space at the end */
2033 if (lstrcmpiW (current, paramStart) == 0) return;
2036 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2041 /*****************************************************************************
2044 * Push a directory onto the stack
2047 void WCMD_pushd (const WCHAR *args)
2049 struct env_stack *curdir;
2051 static const WCHAR parmD[] = {'/','D','\0'};
2053 if (strchrW(args, '/') != NULL) {
2054 SetLastError(ERROR_INVALID_PARAMETER);
2059 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2060 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2061 if( !curdir || !thisdir ) {
2064 WINE_ERR ("out of memory\n");
2068 /* Change directory using CD code with /D parameter */
2069 strcpyW(quals, parmD);
2070 GetCurrentDirectoryW (1024, thisdir);
2072 WCMD_setshow_default(args);
2078 curdir -> next = pushd_directories;
2079 curdir -> strings = thisdir;
2080 if (pushd_directories == NULL) {
2081 curdir -> u.stackdepth = 1;
2083 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2085 pushd_directories = curdir;
2090 /*****************************************************************************
2093 * Pop a directory from the stack
2096 void WCMD_popd (void) {
2097 struct env_stack *temp = pushd_directories;
2099 if (!pushd_directories)
2102 /* pop the old environment from the stack, and make it the current dir */
2103 pushd_directories = temp->next;
2104 SetCurrentDirectoryW(temp->strings);
2105 LocalFree (temp->strings);
2109 /****************************************************************************
2112 * Batch file conditional.
2114 * On entry, cmdlist will point to command containing the IF, and optionally
2115 * the first command to execute (if brackets not found)
2116 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2117 * If ('s were found, execute all within that bracket
2118 * Command may optionally be followed by an ELSE - need to skip instructions
2119 * in the else using the same logic
2121 * FIXME: Much more syntax checking needed!
2124 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
2126 int negate; /* Negate condition */
2127 int test; /* Condition evaluation result */
2128 WCHAR condition[MAX_PATH], *command, *s;
2129 static const WCHAR notW[] = {'n','o','t','\0'};
2130 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2131 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2132 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2133 static const WCHAR eqeqW[] = {'=','=','\0'};
2134 static const WCHAR parmI[] = {'/','I','\0'};
2135 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2137 negate = !lstrcmpiW(param1,notW);
2138 strcpyW(condition, (negate ? param2 : param1));
2139 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2141 if (!lstrcmpiW (condition, errlvlW)) {
2142 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL, FALSE, FALSE);
2144 long int param_int = strtolW(param, &endptr, 10);
2146 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2149 test = ((long int)errorlevel >= param_int);
2150 WCMD_parameter(p, 2+negate, &command, NULL, FALSE, FALSE);
2152 else if (!lstrcmpiW (condition, existW)) {
2153 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE, FALSE))
2154 != INVALID_FILE_ATTRIBUTES);
2155 WCMD_parameter(p, 2+negate, &command, NULL, FALSE, FALSE);
2157 else if (!lstrcmpiW (condition, defdW)) {
2158 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE, FALSE),
2160 WCMD_parameter(p, 2+negate, &command, NULL, FALSE, FALSE);
2162 else if ((s = strstrW (p, eqeqW))) {
2163 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
2164 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
2166 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd, FALSE, FALSE);
2167 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd, FALSE, FALSE);
2168 test = caseInsensitive
2169 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
2170 leftPart, leftPartEnd-leftPart+1,
2171 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
2172 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
2173 leftPart, leftPartEnd-leftPart+1,
2174 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
2175 WCMD_parameter(s, 1, &command, NULL, FALSE, FALSE);
2178 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2182 /* Process rest of IF statement which is on the same line
2183 Note: This may process all or some of the cmdList (eg a GOTO) */
2184 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
2187 /****************************************************************************
2190 * Move a file, directory tree or wildcarded set of files.
2193 void WCMD_move (void)
2196 WIN32_FIND_DATAW fd;
2198 WCHAR input[MAX_PATH];
2199 WCHAR output[MAX_PATH];
2201 WCHAR dir[MAX_PATH];
2202 WCHAR fname[MAX_PATH];
2203 WCHAR ext[MAX_PATH];
2205 if (param1[0] == 0x00) {
2206 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2210 /* If no destination supplied, assume current directory */
2211 if (param2[0] == 0x00) {
2212 strcpyW(param2, dotW);
2215 /* If 2nd parm is directory, then use original filename */
2216 /* Convert partial path to full path */
2217 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2218 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2219 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2220 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2222 /* Split into components */
2223 WCMD_splitpath(input, drive, dir, fname, ext);
2225 hff = FindFirstFileW(input, &fd);
2226 if (hff == INVALID_HANDLE_VALUE)
2230 WCHAR dest[MAX_PATH];
2231 WCHAR src[MAX_PATH];
2235 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2237 /* Build src & dest name */
2238 strcpyW(src, drive);
2241 /* See if dest is an existing directory */
2242 attribs = GetFileAttributesW(output);
2243 if (attribs != INVALID_FILE_ATTRIBUTES &&
2244 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2245 strcpyW(dest, output);
2246 strcatW(dest, slashW);
2247 strcatW(dest, fd.cFileName);
2249 strcpyW(dest, output);
2252 strcatW(src, fd.cFileName);
2254 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2255 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2257 /* If destination exists, prompt unless /Y supplied */
2258 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2260 WCHAR copycmd[MAXSTRING];
2263 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2264 if (strstrW (quals, parmNoY))
2266 else if (strstrW (quals, parmY))
2269 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2270 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2271 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2272 && ! lstrcmpiW (copycmd, parmY));
2275 /* Prompt if overwriting */
2279 /* Ask for confirmation */
2280 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2281 ok = WCMD_ask_confirm(question, FALSE, NULL);
2282 LocalFree(question);
2284 /* So delete the destination prior to the move */
2286 if (!DeleteFileW(dest)) {
2287 WCMD_print_error ();
2296 status = MoveFileW(src, dest);
2298 status = 1; /* Anything other than 0 to prevent error msg below */
2302 WCMD_print_error ();
2305 } while (FindNextFileW(hff, &fd) != 0);
2310 /****************************************************************************
2313 * Suspend execution of a batch script until a key is typed
2316 void WCMD_pause (void)
2322 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2324 have_console = GetConsoleMode(hIn, &oldmode);
2326 SetConsoleMode(hIn, 0);
2328 WCMD_output_asis(anykey);
2329 WCMD_ReadFile(hIn, &key, 1, &count);
2331 SetConsoleMode(hIn, oldmode);
2334 /****************************************************************************
2337 * Delete a directory.
2340 void WCMD_remove_dir (WCHAR *args) {
2343 int argsProcessed = 0;
2345 static const WCHAR parmS[] = {'/','S','\0'};
2346 static const WCHAR parmQ[] = {'/','Q','\0'};
2348 /* Loop through all args */
2350 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, NULL, FALSE, FALSE);
2351 if (argN && argN[0] != '/') {
2352 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2353 wine_dbgstr_w(quals));
2356 /* If subdirectory search not supplied, just try to remove
2357 and report error if it fails (eg if it contains a file) */
2358 if (strstrW (quals, parmS) == NULL) {
2359 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2361 /* Otherwise use ShFileOp to recursively remove a directory */
2364 SHFILEOPSTRUCTW lpDir;
2367 if (strstrW (quals, parmQ) == NULL) {
2369 WCHAR question[MAXSTRING];
2370 static const WCHAR fmt[] = {'%','s',' ','\0'};
2372 /* Ask for confirmation */
2373 wsprintfW(question, fmt, thisArg);
2374 ok = WCMD_ask_confirm(question, TRUE, NULL);
2376 /* Abort if answer is 'N' */
2383 lpDir.pFrom = thisArg;
2384 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2385 lpDir.wFunc = FO_DELETE;
2387 /* SHFileOperationW needs file list with a double null termination */
2388 thisArg[lstrlenW(thisArg) + 1] = 0x00;
2390 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
2395 /* Handle no valid args */
2396 if (argsProcessed == 0) {
2397 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2403 /****************************************************************************
2409 void WCMD_rename (void)
2413 WIN32_FIND_DATAW fd;
2414 WCHAR input[MAX_PATH];
2415 WCHAR *dotDst = NULL;
2417 WCHAR dir[MAX_PATH];
2418 WCHAR fname[MAX_PATH];
2419 WCHAR ext[MAX_PATH];
2423 /* Must be at least two args */
2424 if (param1[0] == 0x00 || param2[0] == 0x00) {
2425 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2430 /* Destination cannot contain a drive letter or directory separator */
2431 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
2432 SetLastError(ERROR_INVALID_PARAMETER);
2438 /* Convert partial path to full path */
2439 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2440 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2441 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
2442 dotDst = strchrW(param2, '.');
2444 /* Split into components */
2445 WCMD_splitpath(input, drive, dir, fname, ext);
2447 hff = FindFirstFileW(input, &fd);
2448 if (hff == INVALID_HANDLE_VALUE)
2452 WCHAR dest[MAX_PATH];
2453 WCHAR src[MAX_PATH];
2454 WCHAR *dotSrc = NULL;
2457 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2459 /* FIXME: If dest name or extension is *, replace with filename/ext
2460 part otherwise use supplied name. This supports:
2462 ren jim.* fred.* etc
2463 However, windows has a more complex algorithm supporting eg
2464 ?'s and *'s mid name */
2465 dotSrc = strchrW(fd.cFileName, '.');
2467 /* Build src & dest name */
2468 strcpyW(src, drive);
2471 dirLen = strlenW(src);
2472 strcatW(src, fd.cFileName);
2475 if (param2[0] == '*') {
2476 strcatW(dest, fd.cFileName);
2477 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
2479 strcatW(dest, param2);
2480 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
2483 /* Build Extension */
2484 if (dotDst && (*(dotDst+1)=='*')) {
2485 if (dotSrc) strcatW(dest, dotSrc);
2486 } else if (dotDst) {
2487 if (dotDst) strcatW(dest, dotDst);
2490 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2491 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2493 status = MoveFileW(src, dest);
2496 WCMD_print_error ();
2499 } while (FindNextFileW(hff, &fd) != 0);
2504 /*****************************************************************************
2507 * Make a copy of the environment.
2509 static WCHAR *WCMD_dupenv( const WCHAR *env )
2519 len += (strlenW(&env[len]) + 1);
2521 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
2524 WINE_ERR("out of memory\n");
2527 memcpy (env_copy, env, len*sizeof (WCHAR));
2533 /*****************************************************************************
2536 * setlocal pushes the environment onto a stack
2537 * Save the environment as unicode so we don't screw anything up.
2539 void WCMD_setlocal (const WCHAR *s) {
2541 struct env_stack *env_copy;
2542 WCHAR cwd[MAX_PATH];
2544 /* setlocal does nothing outside of batch programs */
2545 if (!context) return;
2547 /* DISABLEEXTENSIONS ignored */
2549 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2552 WINE_ERR ("out of memory\n");
2556 env = GetEnvironmentStringsW ();
2557 env_copy->strings = WCMD_dupenv (env);
2558 if (env_copy->strings)
2560 env_copy->batchhandle = context->h;
2561 env_copy->next = saved_environment;
2562 saved_environment = env_copy;
2564 /* Save the current drive letter */
2565 GetCurrentDirectoryW(MAX_PATH, cwd);
2566 env_copy->u.cwd = cwd[0];
2569 LocalFree (env_copy);
2571 FreeEnvironmentStringsW (env);
2575 /*****************************************************************************
2578 * endlocal pops the environment off a stack
2579 * Note: When searching for '=', search from WCHAR position 1, to handle
2580 * special internal environment variables =C:, =D: etc
2582 void WCMD_endlocal (void) {
2583 WCHAR *env, *old, *p;
2584 struct env_stack *temp;
2587 /* setlocal does nothing outside of batch programs */
2588 if (!context) return;
2590 /* setlocal needs a saved environment from within the same context (batch
2591 program) as it was saved in */
2592 if (!saved_environment || saved_environment->batchhandle != context->h)
2595 /* pop the old environment from the stack */
2596 temp = saved_environment;
2597 saved_environment = temp->next;
2599 /* delete the current environment, totally */
2600 env = GetEnvironmentStringsW ();
2601 old = WCMD_dupenv (GetEnvironmentStringsW ());
2604 n = strlenW(&old[len]) + 1;
2605 p = strchrW(&old[len] + 1, '=');
2609 SetEnvironmentVariableW (&old[len], NULL);
2614 FreeEnvironmentStringsW (env);
2616 /* restore old environment */
2617 env = temp->strings;
2620 n = strlenW(&env[len]) + 1;
2621 p = strchrW(&env[len] + 1, '=');
2625 SetEnvironmentVariableW (&env[len], p);
2630 /* Restore current drive letter */
2631 if (IsCharAlphaW(temp->u.cwd)) {
2633 WCHAR cwd[MAX_PATH];
2634 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2636 wsprintfW(envvar, fmt, temp->u.cwd);
2637 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2638 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2639 SetCurrentDirectoryW(cwd);
2647 /*****************************************************************************
2648 * WCMD_setshow_default
2650 * Set/Show the current default directory
2653 void WCMD_setshow_default (const WCHAR *args) {
2659 WIN32_FIND_DATAW fd;
2661 static const WCHAR parmD[] = {'/','D','\0'};
2663 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
2665 /* Skip /D and trailing whitespace if on the front of the command line */
2666 if (CompareStringW(LOCALE_USER_DEFAULT,
2667 NORM_IGNORECASE | SORT_STRINGSORT,
2668 args, 2, parmD, -1) == CSTR_EQUAL) {
2670 while (*args && (*args==' ' || *args=='\t'))
2674 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2675 if (strlenW(args) == 0) {
2676 strcatW (cwd, newlineW);
2677 WCMD_output_asis (cwd);
2680 /* Remove any double quotes, which may be in the
2681 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2684 if (*args != '"') *pos++ = *args;
2687 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2691 /* Search for appropriate directory */
2692 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2693 hff = FindFirstFileW(string, &fd);
2694 if (hff != INVALID_HANDLE_VALUE) {
2696 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2697 WCHAR fpath[MAX_PATH];
2699 WCHAR dir[MAX_PATH];
2700 WCHAR fname[MAX_PATH];
2701 WCHAR ext[MAX_PATH];
2702 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2704 /* Convert path into actual directory spec */
2705 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2706 WCMD_splitpath(fpath, drive, dir, fname, ext);
2709 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2712 } while (FindNextFileW(hff, &fd) != 0);
2716 /* Change to that directory */
2717 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2719 status = SetCurrentDirectoryW(string);
2722 WCMD_print_error ();
2726 /* Save away the actual new directory, to store as current location */
2727 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2729 /* Restore old directory if drive letter would change, and
2730 CD x:\directory /D (or pushd c:\directory) not supplied */
2731 if ((strstrW(quals, parmD) == NULL) &&
2732 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2733 SetCurrentDirectoryW(cwd);
2737 /* Set special =C: type environment variable, for drive letter of
2738 change of directory, even if path was restored due to missing
2739 /D (allows changing drive letter when not resident on that
2741 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2743 strcpyW(env, equalW);
2744 memcpy(env+1, string, 2 * sizeof(WCHAR));
2746 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2747 SetEnvironmentVariableW(env, string);
2754 /****************************************************************************
2757 * Set/Show the system date
2758 * FIXME: Can't change date yet
2761 void WCMD_setshow_date (void) {
2763 WCHAR curdate[64], buffer[64];
2765 static const WCHAR parmT[] = {'/','T','\0'};
2767 if (strlenW(param1) == 0) {
2768 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2769 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2770 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2771 if (strstrW (quals, parmT) == NULL) {
2772 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2773 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2775 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2779 else WCMD_print_error ();
2782 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2786 /****************************************************************************
2788 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
2791 static int WCMD_compare( const void *a, const void *b )
2794 const WCHAR * const *str_a = a, * const *str_b = b;
2795 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2796 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
2797 if( r == CSTR_LESS_THAN ) return -1;
2798 if( r == CSTR_GREATER_THAN ) return 1;
2802 /****************************************************************************
2803 * WCMD_setshow_sortenv
2805 * sort variables into order for display
2806 * Optionally only display those who start with a stub
2807 * returns the count displayed
2809 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2811 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2814 if (stub) stublen = strlenW(stub);
2816 /* count the number of strings, and the total length */
2818 len += (strlenW(&s[len]) + 1);
2822 /* add the strings to an array */
2823 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2827 for( i=1; i<count; i++ )
2828 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2830 /* sort the array */
2831 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2834 for( i=0; i<count; i++ ) {
2835 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2836 NORM_IGNORECASE | SORT_STRINGSORT,
2837 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2838 /* Don't display special internal variables */
2839 if (str[i][0] != '=') {
2840 WCMD_output_asis(str[i]);
2841 WCMD_output_asis(newlineW);
2848 return displayedcount;
2851 /****************************************************************************
2854 * Set/Show the environment variables
2857 void WCMD_setshow_env (WCHAR *s) {
2862 static const WCHAR parmP[] = {'/','P','\0'};
2864 if (param1[0] == 0x00 && quals[0] == 0x00) {
2865 env = GetEnvironmentStringsW();
2866 WCMD_setshow_sortenv( env, NULL );
2870 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2871 if (CompareStringW(LOCALE_USER_DEFAULT,
2872 NORM_IGNORECASE | SORT_STRINGSORT,
2873 s, 2, parmP, -1) == CSTR_EQUAL) {
2874 WCHAR string[MAXSTRING];
2878 while (*s && (*s==' ' || *s=='\t')) s++;
2880 WCMD_strip_quotes(s);
2882 /* If no parameter, or no '=' sign, return an error */
2883 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2884 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2888 /* Output the prompt */
2890 if (strlenW(p) != 0) WCMD_output_asis(p);
2892 /* Read the reply */
2893 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2895 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2896 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2897 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2898 wine_dbgstr_w(string));
2899 status = SetEnvironmentVariableW(s, string);
2906 WCMD_strip_quotes(s);
2907 p = strchrW (s, '=');
2909 env = GetEnvironmentStringsW();
2910 if (WCMD_setshow_sortenv( env, s ) == 0) {
2911 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2918 if (strlenW(p) == 0) p = NULL;
2919 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2921 status = SetEnvironmentVariableW(s, p);
2922 gle = GetLastError();
2923 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2925 } else if ((!status)) WCMD_print_error();
2926 else errorlevel = 0;
2930 /****************************************************************************
2933 * Set/Show the path environment variable
2936 void WCMD_setshow_path (const WCHAR *args) {
2940 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2941 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2943 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
2944 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2946 WCMD_output_asis ( pathEqW);
2947 WCMD_output_asis ( string);
2948 WCMD_output_asis ( newlineW);
2951 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2955 if (*args == '=') args++; /* Skip leading '=' */
2956 status = SetEnvironmentVariableW(pathW, args);
2957 if (!status) WCMD_print_error();
2961 /****************************************************************************
2962 * WCMD_setshow_prompt
2964 * Set or show the command prompt.
2967 void WCMD_setshow_prompt (void) {
2970 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2972 if (strlenW(param1) == 0) {
2973 SetEnvironmentVariableW(promptW, NULL);
2977 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2978 if (strlenW(s) == 0) {
2979 SetEnvironmentVariableW(promptW, NULL);
2981 else SetEnvironmentVariableW(promptW, s);
2985 /****************************************************************************
2988 * Set/Show the system time
2989 * FIXME: Can't change time yet
2992 void WCMD_setshow_time (void) {
2994 WCHAR curtime[64], buffer[64];
2997 static const WCHAR parmT[] = {'/','T','\0'};
2999 if (strlenW(param1) == 0) {
3001 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
3002 curtime, sizeof(curtime)/sizeof(WCHAR))) {
3003 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
3004 if (strstrW (quals, parmT) == NULL) {
3005 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
3006 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3008 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3012 else WCMD_print_error ();
3015 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3019 /****************************************************************************
3022 * Shift batch parameters.
3023 * Optional /n says where to start shifting (n=0-8)
3026 void WCMD_shift (const WCHAR *args) {
3029 if (context != NULL) {
3030 WCHAR *pos = strchrW(args, '/');
3035 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
3036 start = (*(pos+1) - '0');
3038 SetLastError(ERROR_INVALID_PARAMETER);
3043 WINE_TRACE("Shifting variables, starting at %d\n", start);
3044 for (i=start;i<=8;i++) {
3045 context -> shift_count[i] = context -> shift_count[i+1] + 1;
3047 context -> shift_count[9] = context -> shift_count[9] + 1;
3052 /****************************************************************************
3055 void WCMD_start(const WCHAR *args)
3057 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
3058 '\\','s','t','a','r','t','.','e','x','e',0};
3059 WCHAR file[MAX_PATH];
3062 PROCESS_INFORMATION pi;
3064 GetWindowsDirectoryW( file, MAX_PATH );
3065 strcatW( file, exeW );
3066 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
3067 strcpyW( cmdline, file );
3068 strcatW( cmdline, spaceW );
3069 strcatW( cmdline, args );
3071 memset( &st, 0, sizeof(STARTUPINFOW) );
3072 st.cb = sizeof(STARTUPINFOW);
3074 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
3076 WaitForSingleObject( pi.hProcess, INFINITE );
3077 GetExitCodeProcess( pi.hProcess, &errorlevel );
3078 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
3079 CloseHandle(pi.hProcess);
3080 CloseHandle(pi.hThread);
3084 SetLastError(ERROR_FILE_NOT_FOUND);
3085 WCMD_print_error ();
3088 HeapFree( GetProcessHeap(), 0, cmdline );
3091 /****************************************************************************
3094 * Set the console title
3096 void WCMD_title (const WCHAR *args) {
3097 SetConsoleTitleW(args);
3100 /****************************************************************************
3103 * Copy a file to standard output.
3106 void WCMD_type (WCHAR *args) {
3110 BOOL writeHeaders = FALSE;
3112 if (param1[0] == 0x00) {
3113 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3117 if (param2[0] != 0x00) writeHeaders = TRUE;
3119 /* Loop through all args */
3122 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, NULL, FALSE, FALSE);
3130 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3131 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3132 FILE_ATTRIBUTE_NORMAL, NULL);
3133 if (h == INVALID_HANDLE_VALUE) {
3134 WCMD_print_error ();
3135 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3139 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
3140 WCMD_output(fmt, thisArg);
3142 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
3143 if (count == 0) break; /* ReadFile reports success on EOF! */
3145 WCMD_output_asis (buffer);
3152 /****************************************************************************
3155 * Output either a file or stdin to screen in pages
3158 void WCMD_more (WCHAR *args) {
3163 WCHAR moreStrPage[100];
3166 static const WCHAR moreStart[] = {'-','-',' ','\0'};
3167 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
3168 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
3169 ')',' ','-','-','\n','\0'};
3170 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
3172 /* Prefix the NLS more with '-- ', then load the text */
3174 strcpyW(moreStr, moreStart);
3175 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
3176 (sizeof(moreStr)/sizeof(WCHAR))-3);
3178 if (param1[0] == 0x00) {
3180 /* Wine implements pipes via temporary files, and hence stdin is
3181 effectively reading from the file. This means the prompts for
3182 more are satisfied by the next line from the input (file). To
3183 avoid this, ensure stdin is to the console */
3184 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
3185 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
3186 FILE_SHARE_READ, NULL, OPEN_EXISTING,
3187 FILE_ATTRIBUTE_NORMAL, 0);
3188 WINE_TRACE("No parms - working probably in pipe mode\n");
3189 SetStdHandle(STD_INPUT_HANDLE, hConIn);
3191 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
3192 once you get in this bit unless due to a pipe, its going to end badly... */
3193 wsprintfW(moreStrPage, moreFmt, moreStr);
3195 WCMD_enter_paged_mode(moreStrPage);
3196 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3197 if (count == 0) break; /* ReadFile reports success on EOF! */
3199 WCMD_output_asis (buffer);
3201 WCMD_leave_paged_mode();
3203 /* Restore stdin to what it was */
3204 SetStdHandle(STD_INPUT_HANDLE, hstdin);
3205 CloseHandle(hConIn);
3209 BOOL needsPause = FALSE;
3211 /* Loop through all args */
3212 WINE_TRACE("Parms supplied - working through each file\n");
3213 WCMD_enter_paged_mode(moreStrPage);
3216 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, NULL, FALSE, FALSE);
3224 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
3225 WCMD_leave_paged_mode();
3226 WCMD_output_asis(moreStrPage);
3227 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3228 WCMD_enter_paged_mode(moreStrPage);
3232 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3233 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3234 FILE_ATTRIBUTE_NORMAL, NULL);
3235 if (h == INVALID_HANDLE_VALUE) {
3236 WCMD_print_error ();
3237 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3241 ULONG64 fileLen = 0;
3242 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
3244 /* Get the file size */
3245 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
3246 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
3249 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3250 if (count == 0) break; /* ReadFile reports success on EOF! */
3254 /* Update % count (would be used in WCMD_output_asis as prompt) */
3255 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
3257 WCMD_output_asis (buffer);
3263 WCMD_leave_paged_mode();
3267 /****************************************************************************
3270 * Display verify flag.
3271 * FIXME: We don't actually do anything with the verify flag other than toggle
3275 void WCMD_verify (const WCHAR *args) {
3279 count = strlenW(args);
3281 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
3282 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
3285 if (lstrcmpiW(args, onW) == 0) {
3289 else if (lstrcmpiW(args, offW) == 0) {
3290 verify_mode = FALSE;
3293 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
3296 /****************************************************************************
3299 * Display version info.
3302 void WCMD_version (void) {
3304 WCMD_output_asis (version_string);
3308 /****************************************************************************
3311 * Display volume information (set_label = FALSE)
3312 * Additionally set volume label (set_label = TRUE)
3313 * Returns 1 on success, 0 otherwise
3316 int WCMD_volume(BOOL set_label, const WCHAR *path)
3318 DWORD count, serial;
3319 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
3322 if (strlenW(path) == 0) {
3323 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
3325 WCMD_print_error ();
3328 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
3329 &serial, NULL, NULL, NULL, 0);
3332 static const WCHAR fmt[] = {'%','s','\\','\0'};
3333 if ((path[1] != ':') || (strlenW(path) != 2)) {
3334 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
3337 wsprintfW (curdir, fmt, path);
3338 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
3343 WCMD_print_error ();
3346 if (label[0] != '\0') {
3347 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
3351 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
3354 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
3355 HIWORD(serial), LOWORD(serial));
3357 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
3358 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3360 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3361 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3363 if (strlenW(path) != 0) {
3364 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
3367 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
3373 /**************************************************************************
3376 * Exit either the process, or just this batch program
3380 void WCMD_exit (CMD_LIST **cmdList) {
3382 static const WCHAR parmB[] = {'/','B','\0'};
3383 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
3385 if (context && lstrcmpiW(quals, parmB) == 0) {
3387 context -> skip_rest = TRUE;
3395 /*****************************************************************************
3398 * Lists or sets file associations (assoc = TRUE)
3399 * Lists or sets file types (assoc = FALSE)
3401 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
3404 DWORD accessOptions = KEY_READ;
3406 LONG rc = ERROR_SUCCESS;
3407 WCHAR keyValue[MAXSTRING];
3408 DWORD valueLen = MAXSTRING;
3410 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
3411 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
3413 /* See if parameter includes '=' */
3415 newValue = strchrW(args, '=');
3416 if (newValue) accessOptions |= KEY_WRITE;
3418 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
3419 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
3420 accessOptions, &key) != ERROR_SUCCESS) {
3421 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
3425 /* If no parameters then list all associations */
3426 if (*args == 0x00) {
3429 /* Enumerate all the keys */
3430 while (rc != ERROR_NO_MORE_ITEMS) {
3431 WCHAR keyName[MAXSTRING];
3434 /* Find the next value */
3435 nameLen = MAXSTRING;
3436 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
3438 if (rc == ERROR_SUCCESS) {
3440 /* Only interested in extension ones if assoc, or others
3442 if ((keyName[0] == '.' && assoc) ||
3443 (!(keyName[0] == '.') && (!assoc)))
3445 WCHAR subkey[MAXSTRING];
3446 strcpyW(subkey, keyName);
3447 if (!assoc) strcatW(subkey, shOpCmdW);
3449 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3451 valueLen = sizeof(keyValue)/sizeof(WCHAR);
3452 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3453 WCMD_output_asis(keyName);
3454 WCMD_output_asis(equalW);
3455 /* If no default value found, leave line empty after '=' */
3456 if (rc == ERROR_SUCCESS) {
3457 WCMD_output_asis(keyValue);
3459 WCMD_output_asis(newlineW);
3460 RegCloseKey(readKey);
3468 /* Parameter supplied - if no '=' on command line, its a query */
3469 if (newValue == NULL) {
3471 WCHAR subkey[MAXSTRING];
3473 /* Query terminates the parameter at the first space */
3474 strcpyW(keyValue, args);
3475 space = strchrW(keyValue, ' ');
3476 if (space) *space=0x00;
3478 /* Set up key name */
3479 strcpyW(subkey, keyValue);
3480 if (!assoc) strcatW(subkey, shOpCmdW);
3482 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3484 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3485 WCMD_output_asis(args);
3486 WCMD_output_asis(equalW);
3487 /* If no default value found, leave line empty after '=' */
3488 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
3489 WCMD_output_asis(newlineW);
3490 RegCloseKey(readKey);
3493 WCHAR msgbuffer[MAXSTRING];
3495 /* Load the translated 'File association not found' */
3497 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3499 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3501 WCMD_output_stderr(msgbuffer, keyValue);
3505 /* Not a query - its a set or clear of a value */
3508 WCHAR subkey[MAXSTRING];
3510 /* Get pointer to new value */
3514 /* Set up key name */
3515 strcpyW(subkey, args);
3516 if (!assoc) strcatW(subkey, shOpCmdW);
3518 /* If nothing after '=' then clear value - only valid for ASSOC */
3519 if (*newValue == 0x00) {
3521 if (assoc) rc = RegDeleteKeyW(key, args);
3522 if (assoc && rc == ERROR_SUCCESS) {
3523 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
3525 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
3530 WCHAR msgbuffer[MAXSTRING];
3532 /* Load the translated 'File association not found' */
3534 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
3535 sizeof(msgbuffer)/sizeof(WCHAR));
3537 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
3538 sizeof(msgbuffer)/sizeof(WCHAR));
3540 WCMD_output_stderr(msgbuffer, keyValue);
3544 /* It really is a set value = contents */
3546 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
3547 accessOptions, NULL, &readKey, NULL);
3548 if (rc == ERROR_SUCCESS) {
3549 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
3551 sizeof(WCHAR) * (strlenW(newValue) + 1));
3552 RegCloseKey(readKey);
3555 if (rc != ERROR_SUCCESS) {
3559 WCMD_output_asis(args);
3560 WCMD_output_asis(equalW);
3561 WCMD_output_asis(newValue);
3562 WCMD_output_asis(newlineW);
3572 /****************************************************************************
3575 * Colors the terminal screen.
3578 void WCMD_color (void) {
3580 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3581 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3583 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3584 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3588 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3594 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3599 /* Convert the color hex digits */
3600 if (param1[0] == 0x00) {
3601 color = defaultColor;
3603 color = strtoulW(param1, NULL, 16);
3606 /* Fail if fg == bg color */
3607 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3612 /* Set the current screen contents and ensure all future writes
3613 remain this color */
3614 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3615 SetConsoleTextAttribute(hStdOut, color);