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 * command) {
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*) command));
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 preceeding the /a or /b
482 * In addition each filename can contain wildcards
483 * To make matters worse, the + may be in the same parameter (ie no whitespace)
484 * 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 * command) {
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(command, argno++, &rawarg, NULL, TRUE);
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(command, argno++, &rawarg, NULL, TRUE);
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) thisparam = WCMD_parameter(command, argno++, &rawarg, NULL, TRUE);
630 /* We have found something to process - build a COPY_FILE block to store it */
631 thiscopy = HeapAlloc(GetProcessHeap(),0,sizeof(COPY_FILES));
632 if (thiscopy == NULL) goto exitreturn;
635 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
636 thiscopy->concatenate = concatnextfilename;
637 thiscopy->binarycopy = binarymode;
638 thiscopy->next = NULL;
640 /* Time to work out the name. Allocate at least enough space (deliberately too much to
641 leave space to append \* to the end) , then copy in character by character. Strip off
642 quotes if we find them. */
643 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
644 thiscopy->name = HeapAlloc(GetProcessHeap(),0,len*sizeof(WCHAR));
645 memset(thiscopy->name, 0x00, len);
648 pos2 = thiscopy->name;
650 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
652 inquotes = !inquotes;
654 } else *pos2++ = *pos1++;
657 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
659 /* This is either the first source, concatenated subsequent source or destination */
660 if (sourcelist == NULL) {
661 WINE_TRACE("Adding as first source part\n");
662 sourcelist = thiscopy;
663 lastcopyentry = thiscopy;
664 } else if (concatnextfilename) {
665 WINE_TRACE("Adding to source file list to be concatenated\n");
666 lastcopyentry->next = thiscopy;
667 lastcopyentry = thiscopy;
668 } else if (destination == NULL) {
669 destination = thiscopy;
671 /* We have processed sources and destinations and still found more to do - invalid */
672 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
676 concatnextfilename = FALSE;
678 /* We either need to process the rest of the parameter or move to the next */
679 if (*pos1 == '/' || *pos1 == '+') {
683 thisparam = WCMD_parameter(command, argno++, &rawarg, NULL, TRUE);
687 /* Ensure we have at least one source file */
689 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
694 /* Default whether automatic overwriting is on. If we are interactive then
695 we prompt by default, otherwise we overwrite by default
696 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
697 if (opt_noty) prompt = TRUE;
698 else if (opt_y) prompt = FALSE;
700 /* By default, we will force the overwrite in batch mode and ask for
701 * confirmation in interactive mode. */
702 prompt = interactive;
703 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
704 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
705 * default behavior. */
706 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
707 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
708 if (!lstrcmpiW (copycmd, parmY))
710 else if (!lstrcmpiW (copycmd, parmNoY))
715 /* Calculate the destination now - if none supplied, its current dir +
716 filename of first file in list*/
717 if (destination == NULL) {
719 WINE_TRACE("No destination supplied, so need to calculate it\n");
720 strcpyW(destname, dotW);
721 strcatW(destname, slashW);
723 destination = HeapAlloc(GetProcessHeap(),0,sizeof(COPY_FILES));
724 if (destination == NULL) goto exitreturn;
725 destination->concatenate = FALSE; /* Not used for destination */
726 destination->binarycopy = binarymode;
727 destination->next = NULL; /* Not used for destination */
728 destination->name = NULL; /* To be filled in */
729 destisdirectory = TRUE;
735 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
737 /* Convert to fully qualified path/filename */
738 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
739 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
741 /* If parameter is a directory, ensure it ends in \ */
742 attributes = GetFileAttributesW(destname);
743 if ((destname[strlenW(destname) - 1] == '\\') ||
744 ((attributes != INVALID_FILE_ATTRIBUTES) &&
745 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
747 destisdirectory = TRUE;
748 if (!(destname[strlenW(destname) - 1] == '\\')) strcatW(destname, slashW);
749 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
753 /* Normally, the destination is the current directory unless we are
754 concatenating, in which case its current directory plus first filename.
756 In addition by default it is a binary copy unless concatenating, when
757 the copy defaults to an ascii copy (stop at EOF). We do not know the
758 first source part yet (until we search) so flag as needing filling in. */
761 /* We have found an a+b type syntax, so destination has to be a filename
762 and we need to default to ascii copying. If we have been supplied a
763 directory as the destination, we need to defer calculating the name */
764 if (destisdirectory) appendfirstsource = TRUE;
765 if (destination->binarycopy == -1) destination->binarycopy = 0;
767 } else if (!destisdirectory) {
768 /* We have been asked to copy to a filename. Default to ascii IF the
769 source contains wildcards (true even if only one match) */
770 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
771 anyconcats = TRUE; /* We really are concatenating to a single file */
772 if (destination->binarycopy == -1) {
773 destination->binarycopy = 0;
776 if (destination->binarycopy == -1) {
777 destination->binarycopy = 1;
782 /* Save away the destination name*/
783 HeapFree(GetProcessHeap(), 0, destination->name);
784 destination->name = WCMD_strdupW(destname);
785 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
786 wine_dbgstr_w(destname), appendfirstsource);
788 /* Now we need to walk the set of sources, and process each name we come to.
789 If anyconcats is true, we are writing to one file, otherwise we are using
790 the source name each time.
791 If destination exists, prompt for overwrite the first time (if concatenating
792 we ask each time until yes is answered)
793 The first source file we come across must exist (when wildcards expanded)
794 and if concatenating with overwrite prompts, each source file must exist
795 until a yes is answered. */
797 thiscopy = sourcelist;
800 while (thiscopy != NULL) {
802 WCHAR srcpath[MAX_PATH];
806 /* If it was not explicit, we now know whether we are concatenating or not and
807 hence whether to copy as binary or ascii */
808 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
810 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
811 to where the filename portion begins (used for wildcart expansion. */
812 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
813 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
815 /* If parameter is a directory, ensure it ends in \* */
816 attributes = GetFileAttributesW(srcpath);
817 if (srcpath[strlenW(srcpath) - 1] == '\\') {
819 /* We need to know where the filename part starts, so append * and
820 recalculate the full resulting path */
821 strcatW(thiscopy->name, starW);
822 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
823 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
825 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
826 (attributes != INVALID_FILE_ATTRIBUTES) &&
827 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
829 /* We need to know where the filename part starts, so append \* and
830 recalculate the full resulting path */
831 strcatW(thiscopy->name, slashstarW);
832 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
833 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
836 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
837 wine_dbgstr_w(srcpath), anyconcats);
839 /* Loop through all source files */
840 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
841 hff = FindFirstFileW(srcpath, &fd);
842 if (hff != INVALID_HANDLE_VALUE) {
844 WCHAR outname[MAX_PATH];
847 /* Skip . and .., and directories */
848 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
849 WINE_TRACE("Skipping directories\n");
852 /* Build final destination name */
853 strcpyW(outname, destination->name);
854 if (destisdirectory || appendfirstsource) strcatW(outname, fd.cFileName);
856 /* Build source name */
857 strcpyW(filenamepart, fd.cFileName);
859 /* Do we just overwrite */
861 if (anyconcats && writtenoneconcat) {
865 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
866 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
867 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
868 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
870 /* Prompt before overwriting */
872 DWORD attributes = GetFileAttributesW(outname);
873 if (attributes != INVALID_FILE_ATTRIBUTES) {
875 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
876 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
879 else overwrite = TRUE;
882 /* If we needed tyo save away the first filename, do it */
883 if (appendfirstsource && overwrite) {
884 HeapFree(GetProcessHeap(), 0, destination->name);
885 destination->name = WCMD_strdupW(outname);
886 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
887 appendfirstsource = FALSE;
888 destisdirectory = FALSE;
891 /* Do the copy as appropriate */
893 if (anyconcats && writtenoneconcat) {
894 if (thiscopy->binarycopy) {
895 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
897 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
899 } else if (!thiscopy->binarycopy) {
900 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
902 status = CopyFileW(srcpath, outname, FALSE);
908 WINE_TRACE("Copied successfully\n");
909 if (anyconcats) writtenoneconcat = TRUE;
911 /* Append EOF if ascii destination and we are not going to add more onto the end
912 Note: Testing shows windows has an optimization whereas if you have a binary
913 copy of a file to a single destination (ie concatenation) then it does not add
914 the EOF, hence the check on the source copy type below. */
915 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
916 if (!WCMD_AppendEOF(outname)) {
924 } while (FindNextFileW(hff, &fd) != 0);
927 /* Error if the first file was not found */
928 if (!anyconcats || (anyconcats && !writtenoneconcat)) {
934 /* Step on to the next supplied source */
935 thiscopy = thiscopy -> next;
938 /* Append EOF if ascii destination and we were concatenating */
939 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
940 if (!WCMD_AppendEOF(destination->name)) {
946 /* Exit out of the routine, freeing any remaing allocated memory */
949 thiscopy = sourcelist;
950 while (thiscopy != NULL) {
952 /* Free up this block*/
953 thiscopy = thiscopy -> next;
954 HeapFree(GetProcessHeap(), 0, prevcopy->name);
955 HeapFree(GetProcessHeap(), 0, prevcopy);
958 /* Free up the destination memory */
960 HeapFree(GetProcessHeap(), 0, destination->name);
961 HeapFree(GetProcessHeap(), 0, destination);
967 /****************************************************************************
970 * Create a directory (and, if needed, any intermediate directories).
972 * Modifies its argument by replacing slashes temporarily with nulls.
975 static BOOL create_full_path(WCHAR* path)
979 /* don't mess with drive letter portion of path, if any */
984 /* Strip trailing slashes. */
985 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
988 /* Step through path, creating intermediate directories as needed. */
989 /* First component includes drive letter, if any. */
993 /* Skip to end of component */
994 while (*p == '\\') p++;
995 while (*p && *p != '\\') p++;
997 /* path is now the original full path */
998 return CreateDirectoryW(path, NULL);
1000 /* Truncate path, create intermediate directory, and restore path */
1002 rv = CreateDirectoryW(path, NULL);
1004 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1011 void WCMD_create_dir (WCHAR *command) {
1013 WCHAR *argN = command;
1015 if (param1[0] == 0x00) {
1016 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1019 /* Loop through all args */
1021 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL, FALSE);
1023 if (!create_full_path(thisArg)) {
1024 WCMD_print_error ();
1030 /* Parse the /A options given by the user on the commandline
1031 * into a bitmask of wanted attributes (*wantSet),
1032 * and a bitmask of unwanted attributes (*wantClear).
1034 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1035 static const WCHAR parmA[] = {'/','A','\0'};
1038 /* both are strictly 'out' parameters */
1042 /* For each /A argument */
1043 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1044 /* Skip /A itself */
1047 /* Skip optional : */
1050 /* For each of the attribute specifier chars to this /A option */
1051 for (; *p != 0 && *p != '/'; p++) {
1052 BOOL negate = FALSE;
1060 /* Convert the attribute specifier to a bit in one of the masks */
1062 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1063 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1064 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1065 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1067 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1077 /* If filename part of parameter is * or *.*,
1078 * and neither /Q nor /P options were given,
1079 * prompt the user whether to proceed.
1080 * Returns FALSE if user says no, TRUE otherwise.
1081 * *pPrompted is set to TRUE if the user is prompted.
1082 * (If /P supplied, del will prompt for individual files later.)
1084 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1085 static const WCHAR parmP[] = {'/','P','\0'};
1086 static const WCHAR parmQ[] = {'/','Q','\0'};
1088 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1089 static const WCHAR anyExt[]= {'.','*','\0'};
1091 WCHAR dir[MAX_PATH];
1092 WCHAR fname[MAX_PATH];
1093 WCHAR ext[MAX_PATH];
1094 WCHAR fpath[MAX_PATH];
1096 /* Convert path into actual directory spec */
1097 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1098 WCMD_splitpath(fpath, drive, dir, fname, ext);
1100 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1101 if ((strcmpW(fname, starW) == 0) &&
1102 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1104 WCHAR question[MAXSTRING];
1105 static const WCHAR fmt[] = {'%','s',' ','\0'};
1107 /* Caller uses this to suppress "file not found" warning later */
1110 /* Ask for confirmation */
1111 wsprintfW(question, fmt, fpath);
1112 return WCMD_ask_confirm(question, TRUE, NULL);
1115 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1119 /* Helper function for WCMD_delete().
1120 * Deletes a single file, directory, or wildcard.
1121 * If /S was given, does it recursively.
1122 * Returns TRUE if a file was deleted.
1124 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1126 static const WCHAR parmP[] = {'/','P','\0'};
1127 static const WCHAR parmS[] = {'/','S','\0'};
1128 static const WCHAR parmF[] = {'/','F','\0'};
1130 DWORD unwanted_attrs;
1132 WCHAR argCopy[MAX_PATH];
1133 WIN32_FIND_DATAW fd;
1135 WCHAR fpath[MAX_PATH];
1137 BOOL handleParm = TRUE;
1139 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1141 strcpyW(argCopy, thisArg);
1142 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1143 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1145 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1146 /* Skip this arg if user declines to delete *.* */
1150 /* First, try to delete in the current directory */
1151 hff = FindFirstFileW(argCopy, &fd);
1152 if (hff == INVALID_HANDLE_VALUE) {
1158 /* Support del <dirname> by just deleting all files dirname\* */
1160 && (strchrW(argCopy,'*') == NULL)
1161 && (strchrW(argCopy,'?') == NULL)
1162 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1164 WCHAR modifiedParm[MAX_PATH];
1165 static const WCHAR slashStar[] = {'\\','*','\0'};
1167 strcpyW(modifiedParm, argCopy);
1168 strcatW(modifiedParm, slashStar);
1171 WCMD_delete_one(modifiedParm);
1173 } else if (handleParm) {
1175 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1176 strcpyW (fpath, argCopy);
1178 p = strrchrW (fpath, '\\');
1181 strcatW (fpath, fd.cFileName);
1183 else strcpyW (fpath, fd.cFileName);
1184 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1187 /* Handle attribute matching (/A) */
1188 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1189 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1191 /* /P means prompt for each file */
1192 if (ok && strstrW (quals, parmP) != NULL) {
1195 /* Ask for confirmation */
1196 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1197 ok = WCMD_ask_confirm(question, FALSE, NULL);
1198 LocalFree(question);
1201 /* Only proceed if ok to */
1204 /* If file is read only, and /A:r or /F supplied, delete it */
1205 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1206 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1207 strstrW (quals, parmF) != NULL)) {
1208 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1211 /* Now do the delete */
1212 if (!DeleteFileW(fpath)) WCMD_print_error ();
1216 } while (FindNextFileW(hff, &fd) != 0);
1220 /* Now recurse into all subdirectories handling the parameter in the same way */
1221 if (strstrW (quals, parmS) != NULL) {
1223 WCHAR thisDir[MAX_PATH];
1227 WCHAR dir[MAX_PATH];
1228 WCHAR fname[MAX_PATH];
1229 WCHAR ext[MAX_PATH];
1231 /* Convert path into actual directory spec */
1232 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1233 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1235 strcpyW(thisDir, drive);
1236 strcatW(thisDir, dir);
1237 cPos = strlenW(thisDir);
1239 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1241 /* Append '*' to the directory */
1242 thisDir[cPos] = '*';
1243 thisDir[cPos+1] = 0x00;
1245 hff = FindFirstFileW(thisDir, &fd);
1247 /* Remove residual '*' */
1248 thisDir[cPos] = 0x00;
1250 if (hff != INVALID_HANDLE_VALUE) {
1251 DIRECTORY_STACK *allDirs = NULL;
1252 DIRECTORY_STACK *lastEntry = NULL;
1255 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1256 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1257 (strcmpW(fd.cFileName, dotW) != 0)) {
1259 DIRECTORY_STACK *nextDir;
1260 WCHAR subParm[MAX_PATH];
1262 /* Work out search parameter in sub dir */
1263 strcpyW (subParm, thisDir);
1264 strcatW (subParm, fd.cFileName);
1265 strcatW (subParm, slashW);
1266 strcatW (subParm, fname);
1267 strcatW (subParm, ext);
1268 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1270 /* Allocate memory, add to list */
1271 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
1272 if (allDirs == NULL) allDirs = nextDir;
1273 if (lastEntry != NULL) lastEntry->next = nextDir;
1274 lastEntry = nextDir;
1275 nextDir->next = NULL;
1276 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
1277 (strlenW(subParm)+1) * sizeof(WCHAR));
1278 strcpyW(nextDir->dirName, subParm);
1280 } while (FindNextFileW(hff, &fd) != 0);
1283 /* Go through each subdir doing the delete */
1284 while (allDirs != NULL) {
1285 DIRECTORY_STACK *tempDir;
1287 tempDir = allDirs->next;
1288 found |= WCMD_delete_one (allDirs->dirName);
1290 HeapFree(GetProcessHeap(),0,allDirs->dirName);
1291 HeapFree(GetProcessHeap(),0,allDirs);
1300 /****************************************************************************
1303 * Delete a file or wildcarded set.
1306 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1307 * - Each set is a pattern, eg /ahr /as-r means
1308 * readonly+hidden OR nonreadonly system files
1309 * - The '-' applies to a single field, ie /a:-hr means read only
1313 BOOL WCMD_delete (WCHAR *command) {
1316 BOOL argsProcessed = FALSE;
1317 BOOL foundAny = FALSE;
1321 for (argno=0; ; argno++) {
1326 thisArg = WCMD_parameter (command, argno, &argN, NULL, FALSE);
1328 break; /* no more parameters */
1330 continue; /* skip options */
1332 argsProcessed = TRUE;
1333 found = WCMD_delete_one(thisArg);
1336 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1341 /* Handle no valid args */
1343 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1351 * Returns a trimmed version of s with all leading and trailing whitespace removed
1355 static WCHAR *WCMD_strtrim(const WCHAR *s)
1357 DWORD len = strlenW(s);
1358 const WCHAR *start = s;
1361 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
1364 while (isspaceW(*start)) start++;
1366 const WCHAR *end = s + len - 1;
1367 while (end > start && isspaceW(*end)) end--;
1368 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1369 result[end - start + 1] = '\0';
1377 /****************************************************************************
1380 * Echo input to the screen (or not). We don't try to emulate the bugs
1381 * in DOS (try typing "ECHO ON AGAIN" for an example).
1384 void WCMD_echo (const WCHAR *command)
1387 const WCHAR *origcommand = command;
1390 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
1391 || command[0]==':' || command[0]==';')
1394 trimmed = WCMD_strtrim(command);
1395 if (!trimmed) return;
1397 count = strlenW(trimmed);
1398 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1399 && origcommand[0]!=';') {
1400 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1401 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1405 if (lstrcmpiW(trimmed, onW) == 0)
1407 else if (lstrcmpiW(trimmed, offW) == 0)
1410 WCMD_output_asis (command);
1411 WCMD_output_asis (newlineW);
1413 HeapFree(GetProcessHeap(), 0, trimmed);
1416 /*****************************************************************************
1419 * Execute a command, and any && or bracketed follow on to the command. The
1420 * first command to be executed may not be at the front of the
1421 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1423 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1424 const WCHAR *variable, const WCHAR *value,
1425 BOOL isIF, BOOL executecmds)
1427 CMD_LIST *curPosition = *cmdList;
1428 int myDepth = (*cmdList)->bracketDepth;
1430 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1431 cmdList, wine_dbgstr_w(firstcmd),
1432 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1435 /* Skip leading whitespace between condition and the command */
1436 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1438 /* Process the first command, if there is one */
1439 if (executecmds && firstcmd && *firstcmd) {
1440 WCHAR *command = WCMD_strdupW(firstcmd);
1441 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1442 HeapFree(GetProcessHeap(), 0, command);
1446 /* If it didn't move the position, step to next command */
1447 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1449 /* Process any other parts of the command */
1451 BOOL processThese = executecmds;
1454 static const WCHAR ifElse[] = {'e','l','s','e'};
1456 /* execute all appropriate commands */
1457 curPosition = *cmdList;
1459 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1461 (*cmdList)->prevDelim,
1462 (*cmdList)->bracketDepth, myDepth);
1464 /* Execute any statements appended to the line */
1465 /* FIXME: Only if previous call worked for && or failed for || */
1466 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1467 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1468 if (processThese && (*cmdList)->command) {
1469 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1472 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1474 /* Execute any appended to the statement with (...) */
1475 } else if ((*cmdList)->bracketDepth > myDepth) {
1477 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1478 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1480 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1482 /* End of the command - does 'ELSE ' follow as the next command? */
1485 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1486 (*cmdList)->command)) {
1488 /* Swap between if and else processing */
1489 processThese = !processThese;
1491 /* Process the ELSE part */
1493 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1494 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1496 /* Skip leading whitespace between condition and the command */
1497 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1499 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1502 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1504 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1513 /**************************************************************************
1516 * Batch file loop processing.
1518 * On entry: cmdList contains the syntax up to the set
1519 * next cmdList and all in that bracket contain the set data
1520 * next cmdlist contains the DO cmd
1521 * following that is either brackets or && entries (as per if)
1525 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1527 WIN32_FIND_DATAW fd;
1530 static const WCHAR inW[] = {'i','n'};
1531 static const WCHAR doW[] = {'d','o'};
1532 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1536 WCHAR optionsRoot[MAX_PATH];
1537 DIRECTORY_STACK *dirsToWalk = NULL;
1539 BOOL expandDirs = FALSE;
1540 BOOL useNumbers = FALSE;
1541 BOOL doFileset = FALSE;
1542 BOOL doRecurse = FALSE;
1543 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
1544 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1546 CMD_LIST *thisCmdStart;
1547 int parameterNo = 0;
1549 /* Handle optional qualifiers (multiple are allowed) */
1550 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1553 while (thisArg && *thisArg == '/') {
1554 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
1556 switch (toupperW(*thisArg)) {
1557 case 'D': expandDirs = TRUE; break;
1558 case 'L': useNumbers = TRUE; break;
1560 /* Recursive is special case - /R can have an optional path following it */
1561 /* filenamesets are another special case - /F can have an optional options following it */
1565 /* When recursing directories, use current directory as the starting point unless
1566 subsequently overridden */
1567 doRecurse = (toupperW(*thisArg) == 'R');
1568 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
1570 doFileset = (toupperW(*thisArg) == 'F');
1572 /* Retrieve next parameter to see if is root/options (raw form required
1573 with for /f, or unquoted in for /r) */
1574 thisArg = WCMD_parameter(p, parameterNo, NULL, NULL, doFileset);
1576 /* Next parm is either qualifier, path/options or variable -
1577 only care about it if it is the path/options */
1578 if (thisArg && *thisArg != '/' && *thisArg != '%') {
1580 strcpyW(optionsRoot, thisArg);
1582 static unsigned int once;
1583 if (!once++) WINE_FIXME("/F needs to handle options\n");
1589 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
1592 /* Step to next token */
1593 thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1596 /* Ensure line continues with variable */
1597 if (!*thisArg || *thisArg != '%') {
1598 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1602 /* Set up the list of directories to recurse if we are going to */
1604 /* Allocate memory, add to list */
1605 dirsToWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1606 dirsToWalk->next = NULL;
1607 dirsToWalk->dirName = HeapAlloc(GetProcessHeap(),0,
1608 (strlenW(optionsRoot) + 1) * sizeof(WCHAR));
1609 strcpyW(dirsToWalk->dirName, optionsRoot);
1610 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
1613 /* Variable should follow */
1614 strcpyW(variable, thisArg);
1615 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1617 /* Ensure line continues with IN */
1618 thisArg = WCMD_parameter(p, parameterNo++, NULL, NULL, FALSE);
1620 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1621 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
1622 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
1623 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1627 /* Save away where the set of data starts and the variable */
1628 thisDepth = (*cmdList)->bracketDepth;
1629 *cmdList = (*cmdList)->nextcommand;
1630 setStart = (*cmdList);
1632 /* Skip until the close bracket */
1633 WINE_TRACE("Searching %p as the set\n", *cmdList);
1635 (*cmdList)->command != NULL &&
1636 (*cmdList)->bracketDepth > thisDepth) {
1637 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1638 *cmdList = (*cmdList)->nextcommand;
1641 /* Skip the close bracket, if there is one */
1642 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1644 /* Syntax error if missing close bracket, or nothing following it
1645 and once we have the complete set, we expect a DO */
1646 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1647 if ((*cmdList == NULL)
1648 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1650 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1656 /* Loop repeatedly per-directory we are potentially walking, when in for /r
1657 mode, or once for the rest of the time. */
1659 WCHAR fullitem[MAX_PATH];
1661 /* Save away the starting position for the commands (and offset for the
1663 cmdStart = *cmdList;
1664 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1667 /* If we are recursing directories (ie /R), add all sub directories now, then
1668 prefix the root when searching for the item */
1670 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1672 /* Build a generic search and add all directories on the list of directories
1674 strcpyW(fullitem, dirsToWalk->dirName);
1675 strcatW(fullitem, slashstarW);
1676 hff = FindFirstFileW(fullitem, &fd);
1677 if (hff != INVALID_HANDLE_VALUE) {
1679 WINE_TRACE("Looking for subdirectories\n");
1680 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1681 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1682 (strcmpW(fd.cFileName, dotW) != 0))
1684 /* Allocate memory, add to list */
1685 DIRECTORY_STACK *toWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1686 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1687 toWalk->next = remainingDirs->next;
1688 remainingDirs->next = toWalk;
1689 remainingDirs = toWalk;
1690 toWalk->dirName = HeapAlloc(GetProcessHeap(), 0,
1692 (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1693 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1694 strcatW(toWalk->dirName, slashW);
1695 strcatW(toWalk->dirName, fd.cFileName);
1696 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1697 toWalk, toWalk->next);
1699 } while (FindNextFileW(hff, &fd) != 0);
1700 WINE_TRACE("Finished adding all subdirectories\n");
1706 /* Loop through all set entries */
1708 thisSet->command != NULL &&
1709 thisSet->bracketDepth >= thisDepth) {
1711 /* Loop through all entries on the same line */
1715 WINE_TRACE("Processing for set %p\n", thisSet);
1717 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL, TRUE))) {
1720 * If the parameter within the set has a wildcard then search for matching files
1721 * otherwise do a literal substitution.
1723 static const WCHAR wildcards[] = {'*','?','\0'};
1724 thisCmdStart = cmdStart;
1727 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1729 if (!useNumbers && !doFileset) {
1730 WCHAR fullitem[MAX_PATH];
1732 /* Now build the item to use / search for in the specified directory,
1733 as it is fully qualified in the /R case */
1735 strcpyW(fullitem, dirsToWalk->dirName);
1736 strcatW(fullitem, slashW);
1737 strcatW(fullitem, item);
1739 strcpyW(fullitem, item);
1742 if (strpbrkW (fullitem, wildcards)) {
1744 hff = FindFirstFileW(fullitem, &fd);
1745 if (hff != INVALID_HANDLE_VALUE) {
1747 BOOL isDirectory = FALSE;
1749 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1751 /* Handle as files or dirs appropriately, but ignore . and .. */
1752 if (isDirectory == expandDirs &&
1753 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1754 (strcmpW(fd.cFileName, dotW) != 0))
1756 thisCmdStart = cmdStart;
1757 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1760 strcpyW(fullitem, dirsToWalk->dirName);
1761 strcatW(fullitem, slashW);
1762 strcatW(fullitem, fd.cFileName);
1764 strcpyW(fullitem, fd.cFileName);
1767 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1768 fullitem, FALSE, TRUE);
1769 cmdEnd = thisCmdStart;
1771 } while (FindNextFileW(hff, &fd) != 0);
1776 WCMD_part_execute(&thisCmdStart, firstCmd, variable, fullitem, FALSE, TRUE);
1777 cmdEnd = thisCmdStart;
1780 } else if (useNumbers) {
1781 /* Convert the first 3 numbers to signed longs and save */
1782 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1783 /* else ignore them! */
1785 /* Filesets - either a list of files, or a command to run and parse the output */
1786 } else if (doFileset && *itemStart != '"') {
1789 WCHAR temp_file[MAX_PATH];
1791 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1792 wine_dbgstr_w(item));
1794 /* If backquote or single quote, we need to launch that command
1795 and parse the results - use a temporary file */
1796 if (*itemStart == '`' || *itemStart == '\'') {
1798 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1799 static const WCHAR redirOut[] = {'>','%','s','\0'};
1800 static const WCHAR cmdW[] = {'C','M','D','\0'};
1802 /* Remove trailing character */
1803 itemStart[strlenW(itemStart)-1] = 0x00;
1805 /* Get temp filename */
1806 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1807 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1809 /* Execute program and redirect output */
1810 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1811 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1813 /* Open the file, read line by line and process */
1814 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1815 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1818 /* Open the file, read line by line and process */
1819 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1820 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1823 /* Process the input file */
1824 if (input == INVALID_HANDLE_VALUE) {
1825 WCMD_print_error ();
1826 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1828 return; /* FOR loop aborts at first failure here */
1832 WCHAR buffer[MAXSTRING];
1833 WCHAR *where, *parm;
1835 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1837 /* Skip blank lines*/
1838 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
1839 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1840 wine_dbgstr_w(buffer));
1843 /* FIXME: The following should be moved into its own routine and
1844 reused for the string literal parsing below */
1845 thisCmdStart = cmdStart;
1847 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1848 cmdEnd = thisCmdStart;
1854 CloseHandle (input);
1857 /* Delete the temporary file */
1858 if (*itemStart == '`' || *itemStart == '\'') {
1859 DeleteFileW(temp_file);
1862 /* Filesets - A string literal */
1863 } else if (doFileset && *itemStart == '"') {
1864 WCHAR buffer[MAXSTRING];
1865 WCHAR *where, *parm;
1867 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1868 strcpyW(buffer, item);
1869 parm = WCMD_parameter (buffer, 0, &where, NULL, FALSE);
1870 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1871 wine_dbgstr_w(buffer));
1874 /* FIXME: The following should be moved into its own routine and
1875 reused for the string literal parsing below */
1876 thisCmdStart = cmdStart;
1878 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1879 cmdEnd = thisCmdStart;
1883 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1887 /* Move onto the next set line */
1888 thisSet = thisSet->nextcommand;
1891 /* If /L is provided, now run the for loop */
1894 static const WCHAR fmt[] = {'%','d','\0'};
1896 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1897 numbers[0], numbers[2], numbers[1]);
1899 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
1902 sprintfW(thisNum, fmt, i);
1903 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1905 thisCmdStart = cmdStart;
1907 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1909 cmdEnd = thisCmdStart;
1912 /* If we are walking directories, move on to any which remain */
1913 if (dirsToWalk != NULL) {
1914 DIRECTORY_STACK *nextDir = dirsToWalk->next;
1915 HeapFree(GetProcessHeap(), 0, dirsToWalk->dirName);
1916 HeapFree(GetProcessHeap(), 0, dirsToWalk);
1917 dirsToWalk = nextDir;
1918 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
1919 wine_dbgstr_w(dirsToWalk->dirName));
1920 else WINE_TRACE("Finished all directories.\n");
1923 } while (dirsToWalk != NULL);
1925 /* Now skip over the do part if we did not perform the for loop so far.
1926 We store in cmdEnd the next command after the do block, but we only
1927 know this if something was run. If it has not been, we need to calculate
1930 thisCmdStart = cmdStart;
1931 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
1932 WCMD_part_execute(&thisCmdStart, firstCmd, NULL, NULL, FALSE, FALSE);
1933 cmdEnd = thisCmdStart;
1936 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1937 all processing, OR it should be pointing to the end of && processing OR
1938 it should be pointing at the NULL end of bracket for the DO. The return
1939 value needs to be the NEXT command to execute, which it either is, or
1940 we need to step over the closing bracket */
1942 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1945 /**************************************************************************
1948 * Simple on-line help. Help text is stored in the resource file.
1951 void WCMD_give_help (const WCHAR *command)
1955 command = WCMD_skip_leading_spaces((WCHAR*) command);
1956 if (strlenW(command) == 0) {
1957 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1960 /* Display help message for builtin commands */
1961 for (i=0; i<=WCMD_EXIT; i++) {
1962 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1963 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1964 WCMD_output_asis (WCMD_LoadMessage(i));
1968 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1969 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1970 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1971 command, -1, externals[i], -1) == CSTR_EQUAL) {
1973 static const WCHAR helpW[] = {' ', '/','?','\0'};
1974 strcpyW(cmd, command);
1975 strcatW(cmd, helpW);
1976 WCMD_run_program(cmd, FALSE);
1980 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1985 /****************************************************************************
1988 * Batch file jump instruction. Not the most efficient algorithm ;-)
1989 * Prints error message if the specified label cannot be found - the file pointer is
1990 * then at EOF, effectively stopping the batch file.
1991 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1994 void WCMD_goto (CMD_LIST **cmdList) {
1996 WCHAR string[MAX_PATH];
1997 WCHAR current[MAX_PATH];
1999 /* Do not process any more parts of a processed multipart or multilines command */
2000 if (cmdList) *cmdList = NULL;
2002 if (context != NULL) {
2003 WCHAR *paramStart = param1, *str;
2004 static const WCHAR eofW[] = {':','e','o','f','\0'};
2006 if (param1[0] == 0x00) {
2007 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2011 /* Handle special :EOF label */
2012 if (lstrcmpiW (eofW, param1) == 0) {
2013 context -> skip_rest = TRUE;
2017 /* Support goto :label as well as goto label */
2018 if (*paramStart == ':') paramStart++;
2020 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2021 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2023 while (isspaceW (*str)) str++;
2027 while (((current[index] = str[index])) && (!isspaceW (current[index])))
2030 /* ignore space at the end */
2032 if (lstrcmpiW (current, paramStart) == 0) return;
2035 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2040 /*****************************************************************************
2043 * Push a directory onto the stack
2046 void WCMD_pushd (const WCHAR *command)
2048 struct env_stack *curdir;
2050 static const WCHAR parmD[] = {'/','D','\0'};
2052 if (strchrW(command, '/') != NULL) {
2053 SetLastError(ERROR_INVALID_PARAMETER);
2058 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2059 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2060 if( !curdir || !thisdir ) {
2063 WINE_ERR ("out of memory\n");
2067 /* Change directory using CD code with /D parameter */
2068 strcpyW(quals, parmD);
2069 GetCurrentDirectoryW (1024, thisdir);
2071 WCMD_setshow_default(command);
2077 curdir -> next = pushd_directories;
2078 curdir -> strings = thisdir;
2079 if (pushd_directories == NULL) {
2080 curdir -> u.stackdepth = 1;
2082 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2084 pushd_directories = curdir;
2089 /*****************************************************************************
2092 * Pop a directory from the stack
2095 void WCMD_popd (void) {
2096 struct env_stack *temp = pushd_directories;
2098 if (!pushd_directories)
2101 /* pop the old environment from the stack, and make it the current dir */
2102 pushd_directories = temp->next;
2103 SetCurrentDirectoryW(temp->strings);
2104 LocalFree (temp->strings);
2108 /****************************************************************************
2111 * Batch file conditional.
2113 * On entry, cmdlist will point to command containing the IF, and optionally
2114 * the first command to execute (if brackets not found)
2115 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2116 * If ('s were found, execute all within that bracket
2117 * Command may optionally be followed by an ELSE - need to skip instructions
2118 * in the else using the same logic
2120 * FIXME: Much more syntax checking needed!
2123 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
2125 int negate; /* Negate condition */
2126 int test; /* Condition evaluation result */
2127 WCHAR condition[MAX_PATH], *command, *s;
2128 static const WCHAR notW[] = {'n','o','t','\0'};
2129 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2130 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2131 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2132 static const WCHAR eqeqW[] = {'=','=','\0'};
2133 static const WCHAR parmI[] = {'/','I','\0'};
2134 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2136 negate = !lstrcmpiW(param1,notW);
2137 strcpyW(condition, (negate ? param2 : param1));
2138 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2140 if (!lstrcmpiW (condition, errlvlW)) {
2141 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL, FALSE);
2143 long int param_int = strtolW(param, &endptr, 10);
2145 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2148 test = ((long int)errorlevel >= param_int);
2149 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
2151 else if (!lstrcmpiW (condition, existW)) {
2152 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE))
2153 != INVALID_FILE_ATTRIBUTES);
2154 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
2156 else if (!lstrcmpiW (condition, defdW)) {
2157 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL, FALSE),
2159 WCMD_parameter(p, 2+negate, &command, NULL, FALSE);
2161 else if ((s = strstrW (p, eqeqW))) {
2162 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
2163 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
2165 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd, FALSE);
2166 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd, FALSE);
2167 test = caseInsensitive
2168 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
2169 leftPart, leftPartEnd-leftPart+1,
2170 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
2171 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
2172 leftPart, leftPartEnd-leftPart+1,
2173 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
2174 WCMD_parameter(s, 1, &command, NULL, FALSE);
2177 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2181 /* Process rest of IF statement which is on the same line
2182 Note: This may process all or some of the cmdList (eg a GOTO) */
2183 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
2186 /****************************************************************************
2189 * Move a file, directory tree or wildcarded set of files.
2192 void WCMD_move (void)
2195 WIN32_FIND_DATAW fd;
2197 WCHAR input[MAX_PATH];
2198 WCHAR output[MAX_PATH];
2200 WCHAR dir[MAX_PATH];
2201 WCHAR fname[MAX_PATH];
2202 WCHAR ext[MAX_PATH];
2204 if (param1[0] == 0x00) {
2205 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2209 /* If no destination supplied, assume current directory */
2210 if (param2[0] == 0x00) {
2211 strcpyW(param2, dotW);
2214 /* If 2nd parm is directory, then use original filename */
2215 /* Convert partial path to full path */
2216 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2217 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2218 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2219 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2221 /* Split into components */
2222 WCMD_splitpath(input, drive, dir, fname, ext);
2224 hff = FindFirstFileW(input, &fd);
2225 if (hff == INVALID_HANDLE_VALUE)
2229 WCHAR dest[MAX_PATH];
2230 WCHAR src[MAX_PATH];
2234 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2236 /* Build src & dest name */
2237 strcpyW(src, drive);
2240 /* See if dest is an existing directory */
2241 attribs = GetFileAttributesW(output);
2242 if (attribs != INVALID_FILE_ATTRIBUTES &&
2243 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2244 strcpyW(dest, output);
2245 strcatW(dest, slashW);
2246 strcatW(dest, fd.cFileName);
2248 strcpyW(dest, output);
2251 strcatW(src, fd.cFileName);
2253 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2254 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2256 /* If destination exists, prompt unless /Y supplied */
2257 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2259 WCHAR copycmd[MAXSTRING];
2262 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2263 if (strstrW (quals, parmNoY))
2265 else if (strstrW (quals, parmY))
2268 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2269 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2270 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2271 && ! lstrcmpiW (copycmd, parmY));
2274 /* Prompt if overwriting */
2278 /* Ask for confirmation */
2279 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2280 ok = WCMD_ask_confirm(question, FALSE, NULL);
2281 LocalFree(question);
2283 /* So delete the destination prior to the move */
2285 if (!DeleteFileW(dest)) {
2286 WCMD_print_error ();
2295 status = MoveFileW(src, dest);
2297 status = 1; /* Anything other than 0 to prevent error msg below */
2301 WCMD_print_error ();
2304 } while (FindNextFileW(hff, &fd) != 0);
2309 /****************************************************************************
2312 * Suspend execution of a batch script until a key is typed
2315 void WCMD_pause (void)
2321 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2323 have_console = GetConsoleMode(hIn, &oldmode);
2325 SetConsoleMode(hIn, 0);
2327 WCMD_output_asis(anykey);
2328 WCMD_ReadFile(hIn, &key, 1, &count);
2330 SetConsoleMode(hIn, oldmode);
2333 /****************************************************************************
2336 * Delete a directory.
2339 void WCMD_remove_dir (WCHAR *command) {
2342 int argsProcessed = 0;
2343 WCHAR *argN = command;
2344 static const WCHAR parmS[] = {'/','S','\0'};
2345 static const WCHAR parmQ[] = {'/','Q','\0'};
2347 /* Loop through all args */
2349 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
2350 if (argN && argN[0] != '/') {
2351 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2352 wine_dbgstr_w(quals));
2355 /* If subdirectory search not supplied, just try to remove
2356 and report error if it fails (eg if it contains a file) */
2357 if (strstrW (quals, parmS) == NULL) {
2358 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2360 /* Otherwise use ShFileOp to recursively remove a directory */
2363 SHFILEOPSTRUCTW lpDir;
2366 if (strstrW (quals, parmQ) == NULL) {
2368 WCHAR question[MAXSTRING];
2369 static const WCHAR fmt[] = {'%','s',' ','\0'};
2371 /* Ask for confirmation */
2372 wsprintfW(question, fmt, thisArg);
2373 ok = WCMD_ask_confirm(question, TRUE, NULL);
2375 /* Abort if answer is 'N' */
2382 lpDir.pFrom = thisArg;
2383 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2384 lpDir.wFunc = FO_DELETE;
2386 /* SHFileOperationW needs file list with a double null termination */
2387 thisArg[lstrlenW(thisArg) + 1] = 0x00;
2389 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
2394 /* Handle no valid args */
2395 if (argsProcessed == 0) {
2396 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2402 /****************************************************************************
2408 void WCMD_rename (void)
2412 WIN32_FIND_DATAW fd;
2413 WCHAR input[MAX_PATH];
2414 WCHAR *dotDst = NULL;
2416 WCHAR dir[MAX_PATH];
2417 WCHAR fname[MAX_PATH];
2418 WCHAR ext[MAX_PATH];
2422 /* Must be at least two args */
2423 if (param1[0] == 0x00 || param2[0] == 0x00) {
2424 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2429 /* Destination cannot contain a drive letter or directory separator */
2430 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
2431 SetLastError(ERROR_INVALID_PARAMETER);
2437 /* Convert partial path to full path */
2438 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2439 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2440 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
2441 dotDst = strchrW(param2, '.');
2443 /* Split into components */
2444 WCMD_splitpath(input, drive, dir, fname, ext);
2446 hff = FindFirstFileW(input, &fd);
2447 if (hff == INVALID_HANDLE_VALUE)
2451 WCHAR dest[MAX_PATH];
2452 WCHAR src[MAX_PATH];
2453 WCHAR *dotSrc = NULL;
2456 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2458 /* FIXME: If dest name or extension is *, replace with filename/ext
2459 part otherwise use supplied name. This supports:
2461 ren jim.* fred.* etc
2462 However, windows has a more complex algorithm supporting eg
2463 ?'s and *'s mid name */
2464 dotSrc = strchrW(fd.cFileName, '.');
2466 /* Build src & dest name */
2467 strcpyW(src, drive);
2470 dirLen = strlenW(src);
2471 strcatW(src, fd.cFileName);
2474 if (param2[0] == '*') {
2475 strcatW(dest, fd.cFileName);
2476 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
2478 strcatW(dest, param2);
2479 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
2482 /* Build Extension */
2483 if (dotDst && (*(dotDst+1)=='*')) {
2484 if (dotSrc) strcatW(dest, dotSrc);
2485 } else if (dotDst) {
2486 if (dotDst) strcatW(dest, dotDst);
2489 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2490 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2492 status = MoveFileW(src, dest);
2495 WCMD_print_error ();
2498 } while (FindNextFileW(hff, &fd) != 0);
2503 /*****************************************************************************
2506 * Make a copy of the environment.
2508 static WCHAR *WCMD_dupenv( const WCHAR *env )
2518 len += (strlenW(&env[len]) + 1);
2520 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
2523 WINE_ERR("out of memory\n");
2526 memcpy (env_copy, env, len*sizeof (WCHAR));
2532 /*****************************************************************************
2535 * setlocal pushes the environment onto a stack
2536 * Save the environment as unicode so we don't screw anything up.
2538 void WCMD_setlocal (const WCHAR *s) {
2540 struct env_stack *env_copy;
2541 WCHAR cwd[MAX_PATH];
2543 /* setlocal does nothing outside of batch programs */
2544 if (!context) return;
2546 /* DISABLEEXTENSIONS ignored */
2548 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2551 WINE_ERR ("out of memory\n");
2555 env = GetEnvironmentStringsW ();
2556 env_copy->strings = WCMD_dupenv (env);
2557 if (env_copy->strings)
2559 env_copy->batchhandle = context->h;
2560 env_copy->next = saved_environment;
2561 saved_environment = env_copy;
2563 /* Save the current drive letter */
2564 GetCurrentDirectoryW(MAX_PATH, cwd);
2565 env_copy->u.cwd = cwd[0];
2568 LocalFree (env_copy);
2570 FreeEnvironmentStringsW (env);
2574 /*****************************************************************************
2577 * endlocal pops the environment off a stack
2578 * Note: When searching for '=', search from WCHAR position 1, to handle
2579 * special internal environment variables =C:, =D: etc
2581 void WCMD_endlocal (void) {
2582 WCHAR *env, *old, *p;
2583 struct env_stack *temp;
2586 /* setlocal does nothing outside of batch programs */
2587 if (!context) return;
2589 /* setlocal needs a saved environment from within the same context (batch
2590 program) as it was saved in */
2591 if (!saved_environment || saved_environment->batchhandle != context->h)
2594 /* pop the old environment from the stack */
2595 temp = saved_environment;
2596 saved_environment = temp->next;
2598 /* delete the current environment, totally */
2599 env = GetEnvironmentStringsW ();
2600 old = WCMD_dupenv (GetEnvironmentStringsW ());
2603 n = strlenW(&old[len]) + 1;
2604 p = strchrW(&old[len] + 1, '=');
2608 SetEnvironmentVariableW (&old[len], NULL);
2613 FreeEnvironmentStringsW (env);
2615 /* restore old environment */
2616 env = temp->strings;
2619 n = strlenW(&env[len]) + 1;
2620 p = strchrW(&env[len] + 1, '=');
2624 SetEnvironmentVariableW (&env[len], p);
2629 /* Restore current drive letter */
2630 if (IsCharAlphaW(temp->u.cwd)) {
2632 WCHAR cwd[MAX_PATH];
2633 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2635 wsprintfW(envvar, fmt, temp->u.cwd);
2636 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2637 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2638 SetCurrentDirectoryW(cwd);
2646 /*****************************************************************************
2647 * WCMD_setshow_default
2649 * Set/Show the current default directory
2652 void WCMD_setshow_default (const WCHAR *command) {
2658 WIN32_FIND_DATAW fd;
2660 static const WCHAR parmD[] = {'/','D','\0'};
2662 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2664 /* Skip /D and trailing whitespace if on the front of the command line */
2665 if (CompareStringW(LOCALE_USER_DEFAULT,
2666 NORM_IGNORECASE | SORT_STRINGSORT,
2667 command, 2, parmD, -1) == CSTR_EQUAL) {
2669 while (*command && (*command==' ' || *command=='\t'))
2673 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2674 if (strlenW(command) == 0) {
2675 strcatW (cwd, newlineW);
2676 WCMD_output_asis (cwd);
2679 /* Remove any double quotes, which may be in the
2680 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2683 if (*command != '"') *pos++ = *command;
2686 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2690 /* Search for appropriate directory */
2691 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2692 hff = FindFirstFileW(string, &fd);
2693 if (hff != INVALID_HANDLE_VALUE) {
2695 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2696 WCHAR fpath[MAX_PATH];
2698 WCHAR dir[MAX_PATH];
2699 WCHAR fname[MAX_PATH];
2700 WCHAR ext[MAX_PATH];
2701 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2703 /* Convert path into actual directory spec */
2704 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2705 WCMD_splitpath(fpath, drive, dir, fname, ext);
2708 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2711 } while (FindNextFileW(hff, &fd) != 0);
2715 /* Change to that directory */
2716 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2718 status = SetCurrentDirectoryW(string);
2721 WCMD_print_error ();
2725 /* Save away the actual new directory, to store as current location */
2726 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2728 /* Restore old directory if drive letter would change, and
2729 CD x:\directory /D (or pushd c:\directory) not supplied */
2730 if ((strstrW(quals, parmD) == NULL) &&
2731 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2732 SetCurrentDirectoryW(cwd);
2736 /* Set special =C: type environment variable, for drive letter of
2737 change of directory, even if path was restored due to missing
2738 /D (allows changing drive letter when not resident on that
2740 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2742 strcpyW(env, equalW);
2743 memcpy(env+1, string, 2 * sizeof(WCHAR));
2745 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2746 SetEnvironmentVariableW(env, string);
2753 /****************************************************************************
2756 * Set/Show the system date
2757 * FIXME: Can't change date yet
2760 void WCMD_setshow_date (void) {
2762 WCHAR curdate[64], buffer[64];
2764 static const WCHAR parmT[] = {'/','T','\0'};
2766 if (strlenW(param1) == 0) {
2767 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2768 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2769 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2770 if (strstrW (quals, parmT) == NULL) {
2771 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2772 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2774 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2778 else WCMD_print_error ();
2781 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2785 /****************************************************************************
2787 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
2790 static int WCMD_compare( const void *a, const void *b )
2793 const WCHAR * const *str_a = a, * const *str_b = b;
2794 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2795 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
2796 if( r == CSTR_LESS_THAN ) return -1;
2797 if( r == CSTR_GREATER_THAN ) return 1;
2801 /****************************************************************************
2802 * WCMD_setshow_sortenv
2804 * sort variables into order for display
2805 * Optionally only display those who start with a stub
2806 * returns the count displayed
2808 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2810 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2813 if (stub) stublen = strlenW(stub);
2815 /* count the number of strings, and the total length */
2817 len += (strlenW(&s[len]) + 1);
2821 /* add the strings to an array */
2822 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2826 for( i=1; i<count; i++ )
2827 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2829 /* sort the array */
2830 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2833 for( i=0; i<count; i++ ) {
2834 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2835 NORM_IGNORECASE | SORT_STRINGSORT,
2836 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2837 /* Don't display special internal variables */
2838 if (str[i][0] != '=') {
2839 WCMD_output_asis(str[i]);
2840 WCMD_output_asis(newlineW);
2847 return displayedcount;
2850 /****************************************************************************
2853 * Set/Show the environment variables
2856 void WCMD_setshow_env (WCHAR *s) {
2861 static const WCHAR parmP[] = {'/','P','\0'};
2863 if (param1[0] == 0x00 && quals[0] == 0x00) {
2864 env = GetEnvironmentStringsW();
2865 WCMD_setshow_sortenv( env, NULL );
2869 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2870 if (CompareStringW(LOCALE_USER_DEFAULT,
2871 NORM_IGNORECASE | SORT_STRINGSORT,
2872 s, 2, parmP, -1) == CSTR_EQUAL) {
2873 WCHAR string[MAXSTRING];
2877 while (*s && (*s==' ' || *s=='\t')) s++;
2879 WCMD_strip_quotes(s);
2881 /* If no parameter, or no '=' sign, return an error */
2882 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2883 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2887 /* Output the prompt */
2889 if (strlenW(p) != 0) WCMD_output_asis(p);
2891 /* Read the reply */
2892 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2894 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2895 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2896 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2897 wine_dbgstr_w(string));
2898 status = SetEnvironmentVariableW(s, string);
2905 WCMD_strip_quotes(s);
2906 p = strchrW (s, '=');
2908 env = GetEnvironmentStringsW();
2909 if (WCMD_setshow_sortenv( env, s ) == 0) {
2910 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2917 if (strlenW(p) == 0) p = NULL;
2918 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2920 status = SetEnvironmentVariableW(s, p);
2921 gle = GetLastError();
2922 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2924 } else if ((!status)) WCMD_print_error();
2925 else errorlevel = 0;
2929 /****************************************************************************
2932 * Set/Show the path environment variable
2935 void WCMD_setshow_path (const WCHAR *command) {
2939 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2940 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2942 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
2943 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2945 WCMD_output_asis ( pathEqW);
2946 WCMD_output_asis ( string);
2947 WCMD_output_asis ( newlineW);
2950 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2954 if (*command == '=') command++; /* Skip leading '=' */
2955 status = SetEnvironmentVariableW(pathW, command);
2956 if (!status) WCMD_print_error();
2960 /****************************************************************************
2961 * WCMD_setshow_prompt
2963 * Set or show the command prompt.
2966 void WCMD_setshow_prompt (void) {
2969 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2971 if (strlenW(param1) == 0) {
2972 SetEnvironmentVariableW(promptW, NULL);
2976 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2977 if (strlenW(s) == 0) {
2978 SetEnvironmentVariableW(promptW, NULL);
2980 else SetEnvironmentVariableW(promptW, s);
2984 /****************************************************************************
2987 * Set/Show the system time
2988 * FIXME: Can't change time yet
2991 void WCMD_setshow_time (void) {
2993 WCHAR curtime[64], buffer[64];
2996 static const WCHAR parmT[] = {'/','T','\0'};
2998 if (strlenW(param1) == 0) {
3000 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
3001 curtime, sizeof(curtime)/sizeof(WCHAR))) {
3002 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
3003 if (strstrW (quals, parmT) == NULL) {
3004 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
3005 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3007 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3011 else WCMD_print_error ();
3014 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3018 /****************************************************************************
3021 * Shift batch parameters.
3022 * Optional /n says where to start shifting (n=0-8)
3025 void WCMD_shift (const WCHAR *command) {
3028 if (context != NULL) {
3029 WCHAR *pos = strchrW(command, '/');
3034 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
3035 start = (*(pos+1) - '0');
3037 SetLastError(ERROR_INVALID_PARAMETER);
3042 WINE_TRACE("Shifting variables, starting at %d\n", start);
3043 for (i=start;i<=8;i++) {
3044 context -> shift_count[i] = context -> shift_count[i+1] + 1;
3046 context -> shift_count[9] = context -> shift_count[9] + 1;
3051 /****************************************************************************
3054 void WCMD_start(const WCHAR *command)
3056 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
3057 '\\','s','t','a','r','t','.','e','x','e',0};
3058 WCHAR file[MAX_PATH];
3061 PROCESS_INFORMATION pi;
3063 GetWindowsDirectoryW( file, MAX_PATH );
3064 strcatW( file, exeW );
3065 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
3066 strcpyW( cmdline, file );
3067 strcatW( cmdline, spaceW );
3068 strcatW( cmdline, command );
3070 memset( &st, 0, sizeof(STARTUPINFOW) );
3071 st.cb = sizeof(STARTUPINFOW);
3073 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
3075 WaitForSingleObject( pi.hProcess, INFINITE );
3076 GetExitCodeProcess( pi.hProcess, &errorlevel );
3077 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
3078 CloseHandle(pi.hProcess);
3079 CloseHandle(pi.hThread);
3083 SetLastError(ERROR_FILE_NOT_FOUND);
3084 WCMD_print_error ();
3087 HeapFree( GetProcessHeap(), 0, cmdline );
3090 /****************************************************************************
3093 * Set the console title
3095 void WCMD_title (const WCHAR *command) {
3096 SetConsoleTitleW(command);
3099 /****************************************************************************
3102 * Copy a file to standard output.
3105 void WCMD_type (WCHAR *command) {
3108 WCHAR *argN = command;
3109 BOOL writeHeaders = FALSE;
3111 if (param1[0] == 0x00) {
3112 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3116 if (param2[0] != 0x00) writeHeaders = TRUE;
3118 /* Loop through all args */
3121 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
3129 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3130 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3131 FILE_ATTRIBUTE_NORMAL, NULL);
3132 if (h == INVALID_HANDLE_VALUE) {
3133 WCMD_print_error ();
3134 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3138 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
3139 WCMD_output(fmt, thisArg);
3141 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
3142 if (count == 0) break; /* ReadFile reports success on EOF! */
3144 WCMD_output_asis (buffer);
3151 /****************************************************************************
3154 * Output either a file or stdin to screen in pages
3157 void WCMD_more (WCHAR *command) {
3160 WCHAR *argN = command;
3162 WCHAR moreStrPage[100];
3165 static const WCHAR moreStart[] = {'-','-',' ','\0'};
3166 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
3167 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
3168 ')',' ','-','-','\n','\0'};
3169 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
3171 /* Prefix the NLS more with '-- ', then load the text */
3173 strcpyW(moreStr, moreStart);
3174 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
3175 (sizeof(moreStr)/sizeof(WCHAR))-3);
3177 if (param1[0] == 0x00) {
3179 /* Wine implements pipes via temporary files, and hence stdin is
3180 effectively reading from the file. This means the prompts for
3181 more are satisfied by the next line from the input (file). To
3182 avoid this, ensure stdin is to the console */
3183 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
3184 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
3185 FILE_SHARE_READ, NULL, OPEN_EXISTING,
3186 FILE_ATTRIBUTE_NORMAL, 0);
3187 WINE_TRACE("No parms - working probably in pipe mode\n");
3188 SetStdHandle(STD_INPUT_HANDLE, hConIn);
3190 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
3191 once you get in this bit unless due to a pipe, its going to end badly... */
3192 wsprintfW(moreStrPage, moreFmt, moreStr);
3194 WCMD_enter_paged_mode(moreStrPage);
3195 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3196 if (count == 0) break; /* ReadFile reports success on EOF! */
3198 WCMD_output_asis (buffer);
3200 WCMD_leave_paged_mode();
3202 /* Restore stdin to what it was */
3203 SetStdHandle(STD_INPUT_HANDLE, hstdin);
3204 CloseHandle(hConIn);
3208 BOOL needsPause = FALSE;
3210 /* Loop through all args */
3211 WINE_TRACE("Parms supplied - working through each file\n");
3212 WCMD_enter_paged_mode(moreStrPage);
3215 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL, FALSE);
3223 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
3224 WCMD_leave_paged_mode();
3225 WCMD_output_asis(moreStrPage);
3226 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3227 WCMD_enter_paged_mode(moreStrPage);
3231 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3232 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3233 FILE_ATTRIBUTE_NORMAL, NULL);
3234 if (h == INVALID_HANDLE_VALUE) {
3235 WCMD_print_error ();
3236 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3240 ULONG64 fileLen = 0;
3241 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
3243 /* Get the file size */
3244 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
3245 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
3248 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3249 if (count == 0) break; /* ReadFile reports success on EOF! */
3253 /* Update % count (would be used in WCMD_output_asis as prompt) */
3254 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
3256 WCMD_output_asis (buffer);
3262 WCMD_leave_paged_mode();
3266 /****************************************************************************
3269 * Display verify flag.
3270 * FIXME: We don't actually do anything with the verify flag other than toggle
3274 void WCMD_verify (const WCHAR *command) {
3278 count = strlenW(command);
3280 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
3281 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
3284 if (lstrcmpiW(command, onW) == 0) {
3288 else if (lstrcmpiW(command, offW) == 0) {
3289 verify_mode = FALSE;
3292 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
3295 /****************************************************************************
3298 * Display version info.
3301 void WCMD_version (void) {
3303 WCMD_output_asis (version_string);
3307 /****************************************************************************
3310 * Display volume information (set_label = FALSE)
3311 * Additionally set volume label (set_label = TRUE)
3312 * Returns 1 on success, 0 otherwise
3315 int WCMD_volume(BOOL set_label, const WCHAR *path)
3317 DWORD count, serial;
3318 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
3321 if (strlenW(path) == 0) {
3322 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
3324 WCMD_print_error ();
3327 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
3328 &serial, NULL, NULL, NULL, 0);
3331 static const WCHAR fmt[] = {'%','s','\\','\0'};
3332 if ((path[1] != ':') || (strlenW(path) != 2)) {
3333 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
3336 wsprintfW (curdir, fmt, path);
3337 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
3342 WCMD_print_error ();
3345 if (label[0] != '\0') {
3346 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
3350 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
3353 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
3354 HIWORD(serial), LOWORD(serial));
3356 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
3357 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3359 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3360 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3362 if (strlenW(path) != 0) {
3363 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
3366 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
3372 /**************************************************************************
3375 * Exit either the process, or just this batch program
3379 void WCMD_exit (CMD_LIST **cmdList) {
3381 static const WCHAR parmB[] = {'/','B','\0'};
3382 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
3384 if (context && lstrcmpiW(quals, parmB) == 0) {
3386 context -> skip_rest = TRUE;
3394 /*****************************************************************************
3397 * Lists or sets file associations (assoc = TRUE)
3398 * Lists or sets file types (assoc = FALSE)
3400 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
3403 DWORD accessOptions = KEY_READ;
3405 LONG rc = ERROR_SUCCESS;
3406 WCHAR keyValue[MAXSTRING];
3407 DWORD valueLen = MAXSTRING;
3409 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
3410 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
3412 /* See if parameter includes '=' */
3414 newValue = strchrW(command, '=');
3415 if (newValue) accessOptions |= KEY_WRITE;
3417 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
3418 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
3419 accessOptions, &key) != ERROR_SUCCESS) {
3420 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
3424 /* If no parameters then list all associations */
3425 if (*command == 0x00) {
3428 /* Enumerate all the keys */
3429 while (rc != ERROR_NO_MORE_ITEMS) {
3430 WCHAR keyName[MAXSTRING];
3433 /* Find the next value */
3434 nameLen = MAXSTRING;
3435 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
3437 if (rc == ERROR_SUCCESS) {
3439 /* Only interested in extension ones if assoc, or others
3441 if ((keyName[0] == '.' && assoc) ||
3442 (!(keyName[0] == '.') && (!assoc)))
3444 WCHAR subkey[MAXSTRING];
3445 strcpyW(subkey, keyName);
3446 if (!assoc) strcatW(subkey, shOpCmdW);
3448 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3450 valueLen = sizeof(keyValue)/sizeof(WCHAR);
3451 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3452 WCMD_output_asis(keyName);
3453 WCMD_output_asis(equalW);
3454 /* If no default value found, leave line empty after '=' */
3455 if (rc == ERROR_SUCCESS) {
3456 WCMD_output_asis(keyValue);
3458 WCMD_output_asis(newlineW);
3459 RegCloseKey(readKey);
3467 /* Parameter supplied - if no '=' on command line, its a query */
3468 if (newValue == NULL) {
3470 WCHAR subkey[MAXSTRING];
3472 /* Query terminates the parameter at the first space */
3473 strcpyW(keyValue, command);
3474 space = strchrW(keyValue, ' ');
3475 if (space) *space=0x00;
3477 /* Set up key name */
3478 strcpyW(subkey, keyValue);
3479 if (!assoc) strcatW(subkey, shOpCmdW);
3481 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3483 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3484 WCMD_output_asis(command);
3485 WCMD_output_asis(equalW);
3486 /* If no default value found, leave line empty after '=' */
3487 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
3488 WCMD_output_asis(newlineW);
3489 RegCloseKey(readKey);
3492 WCHAR msgbuffer[MAXSTRING];
3494 /* Load the translated 'File association not found' */
3496 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3498 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3500 WCMD_output_stderr(msgbuffer, keyValue);
3504 /* Not a query - its a set or clear of a value */
3507 WCHAR subkey[MAXSTRING];
3509 /* Get pointer to new value */
3513 /* Set up key name */
3514 strcpyW(subkey, command);
3515 if (!assoc) strcatW(subkey, shOpCmdW);
3517 /* If nothing after '=' then clear value - only valid for ASSOC */
3518 if (*newValue == 0x00) {
3520 if (assoc) rc = RegDeleteKeyW(key, command);
3521 if (assoc && rc == ERROR_SUCCESS) {
3522 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
3524 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
3529 WCHAR msgbuffer[MAXSTRING];
3531 /* Load the translated 'File association not found' */
3533 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
3534 sizeof(msgbuffer)/sizeof(WCHAR));
3536 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
3537 sizeof(msgbuffer)/sizeof(WCHAR));
3539 WCMD_output_stderr(msgbuffer, keyValue);
3543 /* It really is a set value = contents */
3545 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
3546 accessOptions, NULL, &readKey, NULL);
3547 if (rc == ERROR_SUCCESS) {
3548 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
3550 sizeof(WCHAR) * (strlenW(newValue) + 1));
3551 RegCloseKey(readKey);
3554 if (rc != ERROR_SUCCESS) {
3558 WCMD_output_asis(command);
3559 WCMD_output_asis(equalW);
3560 WCMD_output_asis(newValue);
3561 WCMD_output_asis(newlineW);
3571 /****************************************************************************
3574 * Colors the terminal screen.
3577 void WCMD_color (void) {
3579 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3580 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3582 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3583 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3587 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3593 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3598 /* Convert the color hex digits */
3599 if (param1[0] == 0x00) {
3600 color = defaultColor;
3602 color = strtoulW(param1, NULL, 16);
3605 /* Fail if fg == bg color */
3606 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3611 /* Set the current screen contents and ensure all future writes
3612 remain this color */
3613 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3614 SetConsoleTextAttribute(hStdOut, color);