2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
37 extern int defaultColor
;
38 extern BOOL echo_mode
;
39 extern BOOL interactive
;
41 struct env_stack
*pushd_directories
;
42 const WCHAR dotW
[] = {'.','\0'};
43 const WCHAR dotdotW
[] = {'.','.','\0'};
44 const WCHAR nullW
[] = {'\0'};
45 const WCHAR starW
[] = {'*','\0'};
46 const WCHAR slashW
[] = {'\\','\0'};
47 const WCHAR equalW
[] = {'=','\0'};
48 const WCHAR wildcardsW
[] = {'*','?','\0'};
49 const WCHAR slashstarW
[] = {'\\','*','\0'};
50 const WCHAR deviceW
[] = {'\\','\\','.','\\','\0'};
51 const WCHAR inbuilt
[][10] = {
52 {'C','A','L','L','\0'},
54 {'C','H','D','I','R','\0'},
56 {'C','O','P','Y','\0'},
57 {'C','T','T','Y','\0'},
58 {'D','A','T','E','\0'},
61 {'E','C','H','O','\0'},
62 {'E','R','A','S','E','\0'},
64 {'G','O','T','O','\0'},
65 {'H','E','L','P','\0'},
67 {'L','A','B','E','L','\0'},
69 {'M','K','D','I','R','\0'},
70 {'M','O','V','E','\0'},
71 {'P','A','T','H','\0'},
72 {'P','A','U','S','E','\0'},
73 {'P','R','O','M','P','T','\0'},
76 {'R','E','N','A','M','E','\0'},
78 {'R','M','D','I','R','\0'},
80 {'S','H','I','F','T','\0'},
81 {'S','T','A','R','T','\0'},
82 {'T','I','M','E','\0'},
83 {'T','I','T','L','E','\0'},
84 {'T','Y','P','E','\0'},
85 {'V','E','R','I','F','Y','\0'},
88 {'E','N','D','L','O','C','A','L','\0'},
89 {'S','E','T','L','O','C','A','L','\0'},
90 {'P','U','S','H','D','\0'},
91 {'P','O','P','D','\0'},
92 {'A','S','S','O','C','\0'},
93 {'C','O','L','O','R','\0'},
94 {'F','T','Y','P','E','\0'},
95 {'M','O','R','E','\0'},
96 {'C','H','O','I','C','E','\0'},
97 {'E','X','I','T','\0'}
99 static const WCHAR externals
[][10] = {
100 {'A','T','T','R','I','B','\0'},
101 {'X','C','O','P','Y','\0'}
103 static const WCHAR onW
[] = {'O','N','\0'};
104 static const WCHAR offW
[] = {'O','F','F','\0'};
105 static const WCHAR parmY
[] = {'/','Y','\0'};
106 static const WCHAR parmNoY
[] = {'/','-','Y','\0'};
107 static const WCHAR eqeqW
[] = {'=','=','\0'};
109 static HINSTANCE hinst
;
110 struct env_stack
*saved_environment
;
111 static BOOL verify_mode
= FALSE
;
113 /* set /a routines work from single character operators, but some of the
114 operators are multiple character ones, especially the assignment ones.
115 Temporarily represent these using the values below on the operator stack */
116 #define OP_POSITIVE 'P'
117 #define OP_NEGATIVE 'N'
118 #define OP_ASSSIGNMUL 'a'
119 #define OP_ASSSIGNDIV 'b'
120 #define OP_ASSSIGNMOD 'c'
121 #define OP_ASSSIGNADD 'd'
122 #define OP_ASSSIGNSUB 'e'
123 #define OP_ASSSIGNAND 'f'
124 #define OP_ASSSIGNNOT 'g'
125 #define OP_ASSSIGNOR 'h'
126 #define OP_ASSSIGNSHL 'i'
127 #define OP_ASSSIGNSHR 'j'
129 /* This maintains a stack of operators, holding both the operator precedence
130 and the single character representation of the operator in question */
131 typedef struct _OPSTACK
135 struct _OPSTACK
*next
;
138 /* This maintains a stack of values, where each value can either be a
139 numeric value, or a string represeting an environment variable */
140 typedef struct _VARSTACK
145 struct _VARSTACK
*next
;
148 /* This maintains a mapping between the calculated operator and the
149 single character representation for the assignment operators. */
154 } calcassignments
[] =
156 {'*', OP_ASSSIGNMUL
},
157 {'/', OP_ASSSIGNDIV
},
158 {'%', OP_ASSSIGNMOD
},
159 {'+', OP_ASSSIGNADD
},
160 {'-', OP_ASSSIGNSUB
},
161 {'&', OP_ASSSIGNAND
},
162 {'^', OP_ASSSIGNNOT
},
164 {'<', OP_ASSSIGNSHL
},
165 {'>', OP_ASSSIGNSHR
},
169 /**************************************************************************
172 * Issue a message and ask for confirmation, waiting on a valid answer.
174 * Returns True if Y (or A) answer is selected
175 * If optionAll contains a pointer, ALL is allowed, and if answered
179 static BOOL
WCMD_ask_confirm (const WCHAR
*message
, BOOL showSureText
,
183 WCHAR confirm
[MAXSTRING
];
184 WCHAR options
[MAXSTRING
];
185 WCHAR Ybuffer
[MAXSTRING
];
186 WCHAR Nbuffer
[MAXSTRING
];
187 WCHAR Abuffer
[MAXSTRING
];
188 WCHAR answer
[MAX_PATH
] = {'\0'};
191 /* Load the translated valid answers */
193 LoadStringW(hinst
, WCMD_CONFIRM
, confirm
, sizeof(confirm
)/sizeof(WCHAR
));
194 msgid
= optionAll
? WCMD_YESNOALL
: WCMD_YESNO
;
195 LoadStringW(hinst
, msgid
, options
, sizeof(options
)/sizeof(WCHAR
));
196 LoadStringW(hinst
, WCMD_YES
, Ybuffer
, sizeof(Ybuffer
)/sizeof(WCHAR
));
197 LoadStringW(hinst
, WCMD_NO
, Nbuffer
, sizeof(Nbuffer
)/sizeof(WCHAR
));
198 LoadStringW(hinst
, WCMD_ALL
, Abuffer
, sizeof(Abuffer
)/sizeof(WCHAR
));
200 /* Loop waiting on a valid answer */
205 WCMD_output_asis (message
);
207 WCMD_output_asis (confirm
);
208 WCMD_output_asis (options
);
209 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
)/sizeof(WCHAR
), &count
);
210 answer
[0] = toupperW(answer
[0]);
211 if (answer
[0] == Ybuffer
[0])
213 if (answer
[0] == Nbuffer
[0])
215 if (optionAll
&& answer
[0] == Abuffer
[0])
223 /****************************************************************************
226 * Clear the terminal screen.
229 void WCMD_clear_screen (void) {
231 /* Emulate by filling the screen from the top left to bottom right with
232 spaces, then moving the cursor to the top left afterwards */
233 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
234 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
236 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
241 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
245 FillConsoleOutputCharacterW(hStdOut
, ' ', screenSize
, topLeft
, &screenSize
);
246 SetConsoleCursorPosition(hStdOut
, topLeft
);
250 /****************************************************************************
253 * Change the default i/o device (ie redirect STDin/STDout).
256 void WCMD_change_tty (void) {
258 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
262 /****************************************************************************
267 void WCMD_choice (const WCHAR
* args
) {
269 static const WCHAR bellW
[] = {7,0};
270 static const WCHAR commaW
[] = {',',0};
271 static const WCHAR bracket_open
[] = {'[',0};
272 static const WCHAR bracket_close
[] = {']','?',0};
277 WCHAR
*my_command
= NULL
;
278 WCHAR opt_default
= 0;
279 DWORD opt_timeout
= 0;
286 have_console
= GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), &oldmode
);
289 my_command
= heap_strdupW(WCMD_skip_leading_spaces((WCHAR
*) args
));
291 ptr
= WCMD_skip_leading_spaces(my_command
);
292 while (*ptr
== '/') {
293 switch (toupperW(ptr
[1])) {
296 /* the colon is optional */
300 if (!*ptr
|| isspaceW(*ptr
)) {
301 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr
));
302 heap_free(my_command
);
306 /* remember the allowed keys (overwrite previous /C option) */
308 while (*ptr
&& (!isspaceW(*ptr
)))
312 /* terminate allowed chars */
314 ptr
= WCMD_skip_leading_spaces(&ptr
[1]);
316 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c
));
321 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
326 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
331 /* the colon is optional */
335 opt_default
= *ptr
++;
337 if (!opt_default
|| (*ptr
!= ',')) {
338 WINE_FIXME("bad option %s for /T\n", opt_default
? wine_dbgstr_w(ptr
) : "");
339 heap_free(my_command
);
345 while (((answer
[count
] = *ptr
)) && isdigitW(*ptr
) && (count
< 15)) {
351 opt_timeout
= atoiW(answer
);
353 ptr
= WCMD_skip_leading_spaces(ptr
);
357 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr
));
358 heap_free(my_command
);
364 WINE_FIXME("timeout not supported: %c,%d\n", opt_default
, opt_timeout
);
367 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), 0);
369 /* use default keys, when needed: localized versions of "Y"es and "No" */
371 LoadStringW(hinst
, WCMD_YES
, buffer
, sizeof(buffer
)/sizeof(WCHAR
));
372 LoadStringW(hinst
, WCMD_NO
, buffer
+ 1, sizeof(buffer
)/sizeof(WCHAR
) - 1);
377 /* print the question, when needed */
379 WCMD_output_asis(ptr
);
383 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c
));
387 /* print a list of all allowed answers inside brackets */
388 WCMD_output_asis(bracket_open
);
391 while ((answer
[0] = *ptr
++)) {
392 WCMD_output_asis(answer
);
394 WCMD_output_asis(commaW
);
396 WCMD_output_asis(bracket_close
);
401 /* FIXME: Add support for option /T */
402 answer
[1] = 0; /* terminate single character string */
403 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, 1, &count
);
406 answer
[0] = toupperW(answer
[0]);
408 ptr
= strchrW(opt_c
, answer
[0]);
410 WCMD_output_asis(answer
);
411 WCMD_output_asis(newlineW
);
413 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), oldmode
);
415 errorlevel
= (ptr
- opt_c
) + 1;
416 WINE_TRACE("answer: %d\n", errorlevel
);
417 heap_free(my_command
);
422 /* key not allowed: play the bell */
423 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer
));
424 WCMD_output_asis(bellW
);
429 /****************************************************************************
432 * Adds an EOF onto the end of a file
433 * Returns TRUE on success
435 static BOOL
WCMD_AppendEOF(WCHAR
*filename
)
442 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename
));
443 h
= CreateFileW(filename
, GENERIC_WRITE
, 0, NULL
,
444 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
447 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename
), GetLastError());
450 SetFilePointer (h
, 0, NULL
, FILE_END
);
451 if (!WriteFile(h
, &eof
, 1, &bytes_written
, NULL
)) {
452 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename
), GetLastError());
461 /****************************************************************************
465 * optionally reading only until EOF (ascii copy)
466 * optionally appending onto an existing file (append)
467 * Returns TRUE on success
469 static BOOL
WCMD_ManualCopy(WCHAR
*srcname
, WCHAR
*dstname
, BOOL ascii
, BOOL append
)
473 DWORD bytesread
, byteswritten
;
475 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
476 wine_dbgstr_w(srcname
), wine_dbgstr_w(dstname
), append
);
478 in
= CreateFileW(srcname
, GENERIC_READ
, 0, NULL
,
479 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
481 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname
), GetLastError());
485 /* Open the output file, overwriting if not appending */
486 out
= CreateFileW(dstname
, GENERIC_WRITE
, 0, NULL
,
487 append
?OPEN_EXISTING
:CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
489 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname
), GetLastError());
494 /* Move to end of destination if we are going to append to it */
496 SetFilePointer(out
, 0, NULL
, FILE_END
);
499 /* Loop copying data from source to destination until EOF read */
502 char buffer
[MAXSTRING
];
504 ok
= ReadFile(in
, buffer
, MAXSTRING
, &bytesread
, NULL
);
507 /* Stop at first EOF */
509 char *ptr
= (char *)memchr((void *)buffer
, '\x1a', bytesread
);
510 if (ptr
) bytesread
= (ptr
- buffer
);
514 ok
= WriteFile(out
, buffer
, bytesread
, &byteswritten
, NULL
);
515 if (!ok
|| byteswritten
!= bytesread
) {
516 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
517 wine_dbgstr_w(dstname
), GetLastError());
521 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
522 wine_dbgstr_w(srcname
), GetLastError());
524 } while (ok
&& bytesread
> 0);
531 /****************************************************************************
534 * Copy a file or wildcarded set.
535 * For ascii/binary type copies, it gets complex:
536 * Syntax on command line is
537 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
538 * Where first /a or /b sets 'mode in operation' until another is found
539 * once another is found, it applies to the file preceding the /a or /b
540 * In addition each filename can contain wildcards
541 * To make matters worse, the + may be in the same parameter (i.e. no
542 * whitespace) or with whitespace separating it
544 * ASCII mode on read == read and stop at first EOF
545 * ASCII mode on write == append EOF to destination
546 * Binary == copy as-is
548 * Design of this is to build up a list of files which will be copied into a
549 * list, then work through the list file by file.
550 * If no destination is specified, it defaults to the name of the first file in
551 * the list, but the current directory.
555 void WCMD_copy(WCHAR
* args
) {
557 BOOL opt_d
, opt_v
, opt_n
, opt_z
, opt_y
, opt_noty
;
562 HANDLE hff
= INVALID_HANDLE_VALUE
;
563 int binarymode
= -1; /* -1 means use the default, 1 is binary, 0 ascii */
564 BOOL concatnextfilename
= FALSE
; /* True if we have just processed a + */
565 BOOL anyconcats
= FALSE
; /* Have we found any + options */
566 BOOL appendfirstsource
= FALSE
; /* Use first found filename as destination */
567 BOOL writtenoneconcat
= FALSE
; /* Remember when the first concatenated file done */
568 BOOL prompt
; /* Prompt before overwriting */
569 WCHAR destname
[MAX_PATH
]; /* Used in calculating the destination name */
570 BOOL destisdirectory
= FALSE
; /* Is the destination a directory? */
574 BOOL dstisdevice
= FALSE
;
575 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
577 typedef struct _COPY_FILES
579 struct _COPY_FILES
*next
;
584 COPY_FILES
*sourcelist
= NULL
;
585 COPY_FILES
*lastcopyentry
= NULL
;
586 COPY_FILES
*destination
= NULL
;
587 COPY_FILES
*thiscopy
= NULL
;
588 COPY_FILES
*prevcopy
= NULL
;
590 /* Assume we were successful! */
593 /* If no args supplied at all, report an error */
594 if (param1
[0] == 0x00) {
595 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG
));
600 opt_d
= opt_v
= opt_n
= opt_z
= opt_y
= opt_noty
= FALSE
;
602 /* Walk through all args, building up a list of files to process */
603 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
604 while (*(thisparam
)) {
608 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam
));
610 /* Handle switches */
611 if (*thisparam
== '/') {
612 while (*thisparam
== '/') {
614 if (toupperW(*thisparam
) == 'D') {
616 if (opt_d
) WINE_FIXME("copy /D support not implemented yet\n");
617 } else if (toupperW(*thisparam
) == 'Y') {
619 } else if (toupperW(*thisparam
) == '-' && toupperW(*(thisparam
+1)) == 'Y') {
621 } else if (toupperW(*thisparam
) == 'V') {
623 if (opt_v
) WINE_FIXME("copy /V support not implemented yet\n");
624 } else if (toupperW(*thisparam
) == 'N') {
626 if (opt_n
) WINE_FIXME("copy /N support not implemented yet\n");
627 } else if (toupperW(*thisparam
) == 'Z') {
629 if (opt_z
) WINE_FIXME("copy /Z support not implemented yet\n");
630 } else if (toupperW(*thisparam
) == 'A') {
631 if (binarymode
!= 0) {
633 WINE_TRACE("Subsequent files will be handled as ASCII\n");
634 if (destination
!= NULL
) {
635 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination
->name
));
636 destination
->binarycopy
= binarymode
;
637 } else if (lastcopyentry
!= NULL
) {
638 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry
->name
));
639 lastcopyentry
->binarycopy
= binarymode
;
642 } else if (toupperW(*thisparam
) == 'B') {
643 if (binarymode
!= 1) {
645 WINE_TRACE("Subsequent files will be handled as binary\n");
646 if (destination
!= NULL
) {
647 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination
->name
));
648 destination
->binarycopy
= binarymode
;
649 } else if (lastcopyentry
!= NULL
) {
650 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry
->name
));
651 lastcopyentry
->binarycopy
= binarymode
;
655 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam
));
660 /* This parameter was purely switches, get the next one */
661 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
665 /* We have found something which is not a switch. If could be anything of the form
666 sourcefilename (which could be destination too)
667 + (when filename + filename syntex used)
668 sourcefilename+sourcefilename
670 +/b[tests show windows then ignores to end of parameter]
673 if (*thisparam
=='+') {
674 if (lastcopyentry
== NULL
) {
675 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
679 concatnextfilename
= TRUE
;
683 /* Move to next thing to process */
685 if (*thisparam
== 0x00)
686 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
690 /* We have found something to process - build a COPY_FILE block to store it */
691 thiscopy
= heap_alloc(sizeof(COPY_FILES
));
693 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam
));
694 thiscopy
->concatenate
= concatnextfilename
;
695 thiscopy
->binarycopy
= binarymode
;
696 thiscopy
->next
= NULL
;
698 /* Time to work out the name. Allocate at least enough space (deliberately too much to
699 leave space to append \* to the end) , then copy in character by character. Strip off
700 quotes if we find them. */
701 len
= strlenW(thisparam
) + (sizeof(WCHAR
) * 5); /* 5 spare characters, null + \*.* */
702 thiscopy
->name
= heap_alloc(len
*sizeof(WCHAR
));
703 memset(thiscopy
->name
, 0x00, len
);
706 pos2
= thiscopy
->name
;
708 while (*pos1
&& (inquotes
|| (*pos1
!= '+' && *pos1
!= '/'))) {
710 inquotes
= !inquotes
;
712 } else *pos2
++ = *pos1
++;
715 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy
->name
));
717 /* This is either the first source, concatenated subsequent source or destination */
718 if (sourcelist
== NULL
) {
719 WINE_TRACE("Adding as first source part\n");
720 sourcelist
= thiscopy
;
721 lastcopyentry
= thiscopy
;
722 } else if (concatnextfilename
) {
723 WINE_TRACE("Adding to source file list to be concatenated\n");
724 lastcopyentry
->next
= thiscopy
;
725 lastcopyentry
= thiscopy
;
726 } else if (destination
== NULL
) {
727 destination
= thiscopy
;
729 /* We have processed sources and destinations and still found more to do - invalid */
730 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
734 concatnextfilename
= FALSE
;
736 /* We either need to process the rest of the parameter or move to the next */
737 if (*pos1
== '/' || *pos1
== '+') {
741 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
745 /* Ensure we have at least one source file */
747 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
752 /* Default whether automatic overwriting is on. If we are interactive then
753 we prompt by default, otherwise we overwrite by default
754 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
755 if (opt_noty
) prompt
= TRUE
;
756 else if (opt_y
) prompt
= FALSE
;
758 /* By default, we will force the overwrite in batch mode and ask for
759 * confirmation in interactive mode. */
760 prompt
= interactive
;
761 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
762 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
763 * default behavior. */
764 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
765 if (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))) {
766 if (!lstrcmpiW (copycmd
, parmY
))
768 else if (!lstrcmpiW (copycmd
, parmNoY
))
773 /* Calculate the destination now - if none supplied, it's current dir +
774 filename of first file in list*/
775 if (destination
== NULL
) {
777 WINE_TRACE("No destination supplied, so need to calculate it\n");
778 strcpyW(destname
, dotW
);
779 strcatW(destname
, slashW
);
781 destination
= heap_alloc(sizeof(COPY_FILES
));
782 if (destination
== NULL
) goto exitreturn
;
783 destination
->concatenate
= FALSE
; /* Not used for destination */
784 destination
->binarycopy
= binarymode
;
785 destination
->next
= NULL
; /* Not used for destination */
786 destination
->name
= NULL
; /* To be filled in */
787 destisdirectory
= TRUE
;
793 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
795 /* Convert to fully qualified path/filename */
796 GetFullPathNameW(destination
->name
, sizeof(destname
)/sizeof(WCHAR
), destname
, &filenamepart
);
797 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname
));
799 /* If parameter is a directory, ensure it ends in \ */
800 attributes
= GetFileAttributesW(destname
);
801 if (ends_with_backslash( destname
) ||
802 ((attributes
!= INVALID_FILE_ATTRIBUTES
) &&
803 (attributes
& FILE_ATTRIBUTE_DIRECTORY
))) {
805 destisdirectory
= TRUE
;
806 if (!ends_with_backslash( destname
)) strcatW(destname
, slashW
);
807 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname
));
811 /* Normally, the destination is the current directory unless we are
812 concatenating, in which case it's current directory plus first filename.
814 In addition by default it is a binary copy unless concatenating, when
815 the copy defaults to an ascii copy (stop at EOF). We do not know the
816 first source part yet (until we search) so flag as needing filling in. */
819 /* We have found an a+b type syntax, so destination has to be a filename
820 and we need to default to ascii copying. If we have been supplied a
821 directory as the destination, we need to defer calculating the name */
822 if (destisdirectory
) appendfirstsource
= TRUE
;
823 if (destination
->binarycopy
== -1) destination
->binarycopy
= 0;
825 } else if (!destisdirectory
) {
826 /* We have been asked to copy to a filename. Default to ascii IF the
827 source contains wildcards (true even if only one match) */
828 if (strpbrkW(sourcelist
->name
, wildcardsW
) != NULL
) {
829 anyconcats
= TRUE
; /* We really are concatenating to a single file */
830 if (destination
->binarycopy
== -1) {
831 destination
->binarycopy
= 0;
834 if (destination
->binarycopy
== -1) {
835 destination
->binarycopy
= 1;
840 /* Save away the destination name*/
841 heap_free(destination
->name
);
842 destination
->name
= heap_strdupW(destname
);
843 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
844 wine_dbgstr_w(destname
), appendfirstsource
);
846 /* Remember if the destination is a device */
847 if (strncmpW(destination
->name
, deviceW
, strlenW(deviceW
)) == 0) {
848 WINE_TRACE("Destination is a device\n");
852 /* Now we need to walk the set of sources, and process each name we come to.
853 If anyconcats is true, we are writing to one file, otherwise we are using
854 the source name each time.
855 If destination exists, prompt for overwrite the first time (if concatenating
856 we ask each time until yes is answered)
857 The first source file we come across must exist (when wildcards expanded)
858 and if concatenating with overwrite prompts, each source file must exist
859 until a yes is answered. */
861 thiscopy
= sourcelist
;
864 while (thiscopy
!= NULL
) {
866 WCHAR srcpath
[MAX_PATH
];
867 const WCHAR
*srcname
;
870 BOOL srcisdevice
= FALSE
;
872 /* If it was not explicit, we now know whether we are concatenating or not and
873 hence whether to copy as binary or ascii */
874 if (thiscopy
->binarycopy
== -1) thiscopy
->binarycopy
= !anyconcats
;
876 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
877 to where the filename portion begins (used for wildcard expansion). */
878 GetFullPathNameW(thiscopy
->name
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, &filenamepart
);
879 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath
));
881 /* If parameter is a directory, ensure it ends in \* */
882 attributes
= GetFileAttributesW(srcpath
);
883 if (ends_with_backslash( srcpath
)) {
885 /* We need to know where the filename part starts, so append * and
886 recalculate the full resulting path */
887 strcatW(thiscopy
->name
, starW
);
888 GetFullPathNameW(thiscopy
->name
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, &filenamepart
);
889 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath
));
891 } else if ((strpbrkW(srcpath
, wildcardsW
) == NULL
) &&
892 (attributes
!= INVALID_FILE_ATTRIBUTES
) &&
893 (attributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
895 /* We need to know where the filename part starts, so append \* and
896 recalculate the full resulting path */
897 strcatW(thiscopy
->name
, slashstarW
);
898 GetFullPathNameW(thiscopy
->name
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, &filenamepart
);
899 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath
));
902 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
903 wine_dbgstr_w(srcpath
), anyconcats
);
905 /* If the source is a device, just use it, otherwise search */
906 if (strncmpW(srcpath
, deviceW
, strlenW(deviceW
)) == 0) {
907 WINE_TRACE("Source is a device\n");
909 srcname
= &srcpath
[4]; /* After the \\.\ prefix */
912 /* Loop through all source files */
913 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath
));
914 hff
= FindFirstFileW(srcpath
, &fd
);
915 if (hff
!= INVALID_HANDLE_VALUE
) {
916 srcname
= fd
.cFileName
;
920 if (srcisdevice
|| hff
!= INVALID_HANDLE_VALUE
) {
922 WCHAR outname
[MAX_PATH
];
925 /* Skip . and .., and directories */
926 if (!srcisdevice
&& fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
927 WINE_TRACE("Skipping directories\n");
930 /* Build final destination name */
931 strcpyW(outname
, destination
->name
);
932 if (destisdirectory
|| appendfirstsource
) strcatW(outname
, srcname
);
934 /* Build source name */
935 if (!srcisdevice
) strcpyW(filenamepart
, srcname
);
937 /* Do we just overwrite (we do if we are writing to a device) */
939 if (dstisdevice
|| (anyconcats
&& writtenoneconcat
)) {
943 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath
));
944 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname
));
945 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
946 thiscopy
->binarycopy
, destination
->binarycopy
, overwrite
, prompt
);
948 /* Prompt before overwriting */
950 DWORD attributes
= GetFileAttributesW(outname
);
951 if (attributes
!= INVALID_FILE_ATTRIBUTES
) {
953 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), outname
);
954 overwrite
= WCMD_ask_confirm(question
, FALSE
, NULL
);
957 else overwrite
= TRUE
;
960 /* If we needed to save away the first filename, do it */
961 if (appendfirstsource
&& overwrite
) {
962 heap_free(destination
->name
);
963 destination
->name
= heap_strdupW(outname
);
964 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname
));
965 appendfirstsource
= FALSE
;
966 destisdirectory
= FALSE
;
969 /* Do the copy as appropriate */
971 if (anyconcats
&& writtenoneconcat
) {
972 if (thiscopy
->binarycopy
) {
973 status
= WCMD_ManualCopy(srcpath
, outname
, FALSE
, TRUE
);
975 status
= WCMD_ManualCopy(srcpath
, outname
, TRUE
, TRUE
);
977 } else if (!thiscopy
->binarycopy
) {
978 status
= WCMD_ManualCopy(srcpath
, outname
, TRUE
, FALSE
);
979 } else if (srcisdevice
) {
980 status
= WCMD_ManualCopy(srcpath
, outname
, FALSE
, FALSE
);
982 status
= CopyFileW(srcpath
, outname
, FALSE
);
988 WINE_TRACE("Copied successfully\n");
989 if (anyconcats
) writtenoneconcat
= TRUE
;
991 /* Append EOF if ascii destination and we are not going to add more onto the end
992 Note: Testing shows windows has an optimization whereas if you have a binary
993 copy of a file to a single destination (ie concatenation) then it does not add
994 the EOF, hence the check on the source copy type below. */
995 if (!destination
->binarycopy
&& !anyconcats
&& !thiscopy
->binarycopy
) {
996 if (!WCMD_AppendEOF(outname
)) {
1004 } while (!srcisdevice
&& FindNextFileW(hff
, &fd
) != 0);
1005 if (!srcisdevice
) FindClose (hff
);
1007 /* Error if the first file was not found */
1008 if (!anyconcats
|| (anyconcats
&& !writtenoneconcat
)) {
1009 WCMD_print_error ();
1014 /* Step on to the next supplied source */
1015 thiscopy
= thiscopy
-> next
;
1018 /* Append EOF if ascii destination and we were concatenating */
1019 if (!errorlevel
&& !destination
->binarycopy
&& anyconcats
&& writtenoneconcat
) {
1020 if (!WCMD_AppendEOF(destination
->name
)) {
1021 WCMD_print_error ();
1026 /* Exit out of the routine, freeing any remaining allocated memory */
1029 thiscopy
= sourcelist
;
1030 while (thiscopy
!= NULL
) {
1031 prevcopy
= thiscopy
;
1032 /* Free up this block*/
1033 thiscopy
= thiscopy
-> next
;
1034 heap_free(prevcopy
->name
);
1035 heap_free(prevcopy
);
1038 /* Free up the destination memory */
1040 heap_free(destination
->name
);
1041 heap_free(destination
);
1047 /****************************************************************************
1050 * Create a directory (and, if needed, any intermediate directories).
1052 * Modifies its argument by replacing slashes temporarily with nulls.
1055 static BOOL
create_full_path(WCHAR
* path
)
1059 /* don't mess with drive letter portion of path, if any */
1064 /* Strip trailing slashes. */
1065 for (p
= path
+ strlenW(path
) - 1; p
!= start
&& *p
== '\\'; p
--)
1068 /* Step through path, creating intermediate directories as needed. */
1069 /* First component includes drive letter, if any. */
1073 /* Skip to end of component */
1074 while (*p
== '\\') p
++;
1075 while (*p
&& *p
!= '\\') p
++;
1077 /* path is now the original full path */
1078 return CreateDirectoryW(path
, NULL
);
1080 /* Truncate path, create intermediate directory, and restore path */
1082 rv
= CreateDirectoryW(path
, NULL
);
1084 if (!rv
&& GetLastError() != ERROR_ALREADY_EXISTS
)
1091 void WCMD_create_dir (WCHAR
*args
) {
1095 if (param1
[0] == 0x00) {
1096 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1099 /* Loop through all args */
1101 WCHAR
*thisArg
= WCMD_parameter(args
, argno
++, &argN
, FALSE
, FALSE
);
1103 if (!create_full_path(thisArg
)) {
1104 WCMD_print_error ();
1110 /* Parse the /A options given by the user on the commandline
1111 * into a bitmask of wanted attributes (*wantSet),
1112 * and a bitmask of unwanted attributes (*wantClear).
1114 static void WCMD_delete_parse_attributes(DWORD
*wantSet
, DWORD
*wantClear
) {
1115 static const WCHAR parmA
[] = {'/','A','\0'};
1118 /* both are strictly 'out' parameters */
1122 /* For each /A argument */
1123 for (p
=strstrW(quals
, parmA
); p
!= NULL
; p
=strstrW(p
, parmA
)) {
1124 /* Skip /A itself */
1127 /* Skip optional : */
1130 /* For each of the attribute specifier chars to this /A option */
1131 for (; *p
!= 0 && *p
!= '/'; p
++) {
1132 BOOL negate
= FALSE
;
1140 /* Convert the attribute specifier to a bit in one of the masks */
1142 case 'R': mask
= FILE_ATTRIBUTE_READONLY
; break;
1143 case 'H': mask
= FILE_ATTRIBUTE_HIDDEN
; break;
1144 case 'S': mask
= FILE_ATTRIBUTE_SYSTEM
; break;
1145 case 'A': mask
= FILE_ATTRIBUTE_ARCHIVE
; break;
1147 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
1157 /* If filename part of parameter is * or *.*,
1158 * and neither /Q nor /P options were given,
1159 * prompt the user whether to proceed.
1160 * Returns FALSE if user says no, TRUE otherwise.
1161 * *pPrompted is set to TRUE if the user is prompted.
1162 * (If /P supplied, del will prompt for individual files later.)
1164 static BOOL
WCMD_delete_confirm_wildcard(const WCHAR
*filename
, BOOL
*pPrompted
) {
1165 static const WCHAR parmP
[] = {'/','P','\0'};
1166 static const WCHAR parmQ
[] = {'/','Q','\0'};
1168 if ((strstrW(quals
, parmQ
) == NULL
) && (strstrW(quals
, parmP
) == NULL
)) {
1169 static const WCHAR anyExt
[]= {'.','*','\0'};
1171 WCHAR dir
[MAX_PATH
];
1172 WCHAR fname
[MAX_PATH
];
1173 WCHAR ext
[MAX_PATH
];
1174 WCHAR fpath
[MAX_PATH
];
1176 /* Convert path into actual directory spec */
1177 GetFullPathNameW(filename
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
1178 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
1180 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1181 if ((strcmpW(fname
, starW
) == 0) &&
1182 (*ext
== 0x00 || (strcmpW(ext
, anyExt
) == 0))) {
1184 WCHAR question
[MAXSTRING
];
1185 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1187 /* Caller uses this to suppress "file not found" warning later */
1190 /* Ask for confirmation */
1191 wsprintfW(question
, fmt
, fpath
);
1192 return WCMD_ask_confirm(question
, TRUE
, NULL
);
1195 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1199 /* Helper function for WCMD_delete().
1200 * Deletes a single file, directory, or wildcard.
1201 * If /S was given, does it recursively.
1202 * Returns TRUE if a file was deleted.
1204 static BOOL
WCMD_delete_one (const WCHAR
*thisArg
) {
1206 static const WCHAR parmP
[] = {'/','P','\0'};
1207 static const WCHAR parmS
[] = {'/','S','\0'};
1208 static const WCHAR parmF
[] = {'/','F','\0'};
1210 DWORD unwanted_attrs
;
1212 WCHAR argCopy
[MAX_PATH
];
1213 WIN32_FIND_DATAW fd
;
1215 WCHAR fpath
[MAX_PATH
];
1217 BOOL handleParm
= TRUE
;
1219 WCMD_delete_parse_attributes(&wanted_attrs
, &unwanted_attrs
);
1221 strcpyW(argCopy
, thisArg
);
1222 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1223 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
1225 if (!WCMD_delete_confirm_wildcard(argCopy
, &found
)) {
1226 /* Skip this arg if user declines to delete *.* */
1230 /* First, try to delete in the current directory */
1231 hff
= FindFirstFileW(argCopy
, &fd
);
1232 if (hff
== INVALID_HANDLE_VALUE
) {
1238 /* Support del <dirname> by just deleting all files dirname\* */
1240 && (strchrW(argCopy
,'*') == NULL
)
1241 && (strchrW(argCopy
,'?') == NULL
)
1242 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
1244 WCHAR modifiedParm
[MAX_PATH
];
1245 static const WCHAR slashStar
[] = {'\\','*','\0'};
1247 strcpyW(modifiedParm
, argCopy
);
1248 strcatW(modifiedParm
, slashStar
);
1251 WCMD_delete_one(modifiedParm
);
1253 } else if (handleParm
) {
1255 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1256 strcpyW (fpath
, argCopy
);
1258 p
= strrchrW (fpath
, '\\');
1261 strcatW (fpath
, fd
.cFileName
);
1263 else strcpyW (fpath
, fd
.cFileName
);
1264 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
1267 /* Handle attribute matching (/A) */
1268 ok
= ((fd
.dwFileAttributes
& wanted_attrs
) == wanted_attrs
)
1269 && ((fd
.dwFileAttributes
& unwanted_attrs
) == 0);
1271 /* /P means prompt for each file */
1272 if (ok
&& strstrW (quals
, parmP
) != NULL
) {
1275 /* Ask for confirmation */
1276 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
1277 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1278 LocalFree(question
);
1281 /* Only proceed if ok to */
1284 /* If file is read only, and /A:r or /F supplied, delete it */
1285 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
1286 ((wanted_attrs
& FILE_ATTRIBUTE_READONLY
) ||
1287 strstrW (quals
, parmF
) != NULL
)) {
1288 SetFileAttributesW(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
1291 /* Now do the delete */
1292 if (!DeleteFileW(fpath
)) WCMD_print_error ();
1296 } while (FindNextFileW(hff
, &fd
) != 0);
1300 /* Now recurse into all subdirectories handling the parameter in the same way */
1301 if (strstrW (quals
, parmS
) != NULL
) {
1303 WCHAR thisDir
[MAX_PATH
];
1307 WCHAR dir
[MAX_PATH
];
1308 WCHAR fname
[MAX_PATH
];
1309 WCHAR ext
[MAX_PATH
];
1311 /* Convert path into actual directory spec */
1312 GetFullPathNameW(argCopy
, sizeof(thisDir
)/sizeof(WCHAR
), thisDir
, NULL
);
1313 WCMD_splitpath(thisDir
, drive
, dir
, fname
, ext
);
1315 strcpyW(thisDir
, drive
);
1316 strcatW(thisDir
, dir
);
1317 cPos
= strlenW(thisDir
);
1319 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
1321 /* Append '*' to the directory */
1322 thisDir
[cPos
] = '*';
1323 thisDir
[cPos
+1] = 0x00;
1325 hff
= FindFirstFileW(thisDir
, &fd
);
1327 /* Remove residual '*' */
1328 thisDir
[cPos
] = 0x00;
1330 if (hff
!= INVALID_HANDLE_VALUE
) {
1331 DIRECTORY_STACK
*allDirs
= NULL
;
1332 DIRECTORY_STACK
*lastEntry
= NULL
;
1335 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
1336 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
1337 (strcmpW(fd
.cFileName
, dotW
) != 0)) {
1339 DIRECTORY_STACK
*nextDir
;
1340 WCHAR subParm
[MAX_PATH
];
1342 /* Work out search parameter in sub dir */
1343 strcpyW (subParm
, thisDir
);
1344 strcatW (subParm
, fd
.cFileName
);
1345 strcatW (subParm
, slashW
);
1346 strcatW (subParm
, fname
);
1347 strcatW (subParm
, ext
);
1348 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
1350 /* Allocate memory, add to list */
1351 nextDir
= heap_alloc(sizeof(DIRECTORY_STACK
));
1352 if (allDirs
== NULL
) allDirs
= nextDir
;
1353 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
1354 lastEntry
= nextDir
;
1355 nextDir
->next
= NULL
;
1356 nextDir
->dirName
= heap_strdupW(subParm
);
1358 } while (FindNextFileW(hff
, &fd
) != 0);
1361 /* Go through each subdir doing the delete */
1362 while (allDirs
!= NULL
) {
1363 DIRECTORY_STACK
*tempDir
;
1365 tempDir
= allDirs
->next
;
1366 found
|= WCMD_delete_one (allDirs
->dirName
);
1368 heap_free(allDirs
->dirName
);
1378 /****************************************************************************
1381 * Delete a file or wildcarded set.
1384 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1385 * - Each set is a pattern, eg /ahr /as-r means
1386 * readonly+hidden OR nonreadonly system files
1387 * - The '-' applies to a single field, ie /a:-hr means read only
1391 BOOL
WCMD_delete (WCHAR
*args
) {
1394 BOOL argsProcessed
= FALSE
;
1395 BOOL foundAny
= FALSE
;
1399 for (argno
=0; ; argno
++) {
1404 thisArg
= WCMD_parameter (args
, argno
, &argN
, FALSE
, FALSE
);
1406 break; /* no more parameters */
1408 continue; /* skip options */
1410 argsProcessed
= TRUE
;
1411 found
= WCMD_delete_one(thisArg
);
1414 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND
), thisArg
);
1419 /* Handle no valid args */
1421 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1429 * Returns a trimmed version of s with all leading and trailing whitespace removed
1433 static WCHAR
*WCMD_strtrim(const WCHAR
*s
)
1435 DWORD len
= strlenW(s
);
1436 const WCHAR
*start
= s
;
1439 result
= heap_alloc((len
+ 1) * sizeof(WCHAR
));
1441 while (isspaceW(*start
)) start
++;
1443 const WCHAR
*end
= s
+ len
- 1;
1444 while (end
> start
&& isspaceW(*end
)) end
--;
1445 memcpy(result
, start
, (end
- start
+ 2) * sizeof(WCHAR
));
1446 result
[end
- start
+ 1] = '\0';
1454 /****************************************************************************
1457 * Echo input to the screen (or not). We don't try to emulate the bugs
1458 * in DOS (try typing "ECHO ON AGAIN" for an example).
1461 void WCMD_echo (const WCHAR
*args
)
1464 const WCHAR
*origcommand
= args
;
1467 if ( args
[0]==' ' || args
[0]=='\t' || args
[0]=='.'
1468 || args
[0]==':' || args
[0]==';')
1471 trimmed
= WCMD_strtrim(args
);
1472 if (!trimmed
) return;
1474 count
= strlenW(trimmed
);
1475 if (count
== 0 && origcommand
[0]!='.' && origcommand
[0]!=':'
1476 && origcommand
[0]!=';') {
1477 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
1478 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
1483 if (lstrcmpiW(trimmed
, onW
) == 0)
1485 else if (lstrcmpiW(trimmed
, offW
) == 0)
1488 WCMD_output_asis (args
);
1489 WCMD_output_asis (newlineW
);
1494 /*****************************************************************************
1497 * Execute a command, and any && or bracketed follow on to the command. The
1498 * first command to be executed may not be at the front of the
1499 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1501 static void WCMD_part_execute(CMD_LIST
**cmdList
, const WCHAR
*firstcmd
,
1502 BOOL isIF
, BOOL executecmds
)
1504 CMD_LIST
*curPosition
= *cmdList
;
1505 int myDepth
= (*cmdList
)->bracketDepth
;
1507 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d)\n", cmdList
, wine_dbgstr_w(firstcmd
),
1510 /* Skip leading whitespace between condition and the command */
1511 while (firstcmd
&& *firstcmd
&& (*firstcmd
==' ' || *firstcmd
=='\t')) firstcmd
++;
1513 /* Process the first command, if there is one */
1514 if (executecmds
&& firstcmd
&& *firstcmd
) {
1515 WCHAR
*command
= heap_strdupW(firstcmd
);
1516 WCMD_execute (firstcmd
, (*cmdList
)->redirects
, cmdList
, FALSE
);
1521 /* If it didn't move the position, step to next command */
1522 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1524 /* Process any other parts of the command */
1526 BOOL processThese
= executecmds
;
1529 static const WCHAR ifElse
[] = {'e','l','s','e'};
1531 /* execute all appropriate commands */
1532 curPosition
= *cmdList
;
1534 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1536 (*cmdList
)->prevDelim
,
1537 (*cmdList
)->bracketDepth
, myDepth
);
1539 /* Execute any statements appended to the line */
1540 /* FIXME: Only if previous call worked for && or failed for || */
1541 if ((*cmdList
)->prevDelim
== CMD_ONFAILURE
||
1542 (*cmdList
)->prevDelim
== CMD_ONSUCCESS
) {
1543 if (processThese
&& (*cmdList
)->command
) {
1544 WCMD_execute ((*cmdList
)->command
, (*cmdList
)->redirects
,
1547 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1549 /* Execute any appended to the statement with (...) */
1550 } else if ((*cmdList
)->bracketDepth
> myDepth
) {
1552 *cmdList
= WCMD_process_commands(*cmdList
, TRUE
, FALSE
);
1553 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList
);
1555 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1557 /* End of the command - does 'ELSE ' follow as the next command? */
1560 && WCMD_keyword_ws_found(ifElse
, sizeof(ifElse
)/sizeof(ifElse
[0]),
1561 (*cmdList
)->command
)) {
1563 /* Swap between if and else processing */
1564 processThese
= !processThese
;
1566 /* Process the ELSE part */
1568 const int keyw_len
= sizeof(ifElse
)/sizeof(ifElse
[0]) + 1;
1569 WCHAR
*cmd
= ((*cmdList
)->command
) + keyw_len
;
1571 /* Skip leading whitespace between condition and the command */
1572 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
1574 WCMD_execute (cmd
, (*cmdList
)->redirects
, cmdList
, FALSE
);
1577 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1579 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
1588 /*****************************************************************************
1589 * WCMD_parse_forf_options
1591 * Parses the for /f 'options', extracting the values and validating the
1592 * keywords. Note all keywords are optional.
1594 * options [I] The unparsed parameter string
1595 * eol [O] Set to the comment character (eol=x)
1596 * skip [O] Set to the number of lines to skip (skip=xx)
1597 * delims [O] Set to the token delimiters (delims=)
1598 * tokens [O] Set to the requested tokens, as provided (tokens=)
1599 * usebackq [O] Set to TRUE if usebackq found
1601 * Returns TRUE on success, FALSE on syntax error
1604 static BOOL
WCMD_parse_forf_options(WCHAR
*options
, WCHAR
*eol
, int *skip
,
1605 WCHAR
*delims
, WCHAR
*tokens
, BOOL
*usebackq
)
1608 WCHAR
*pos
= options
;
1609 int len
= strlenW(pos
);
1610 static const WCHAR eolW
[] = {'e','o','l','='};
1611 static const WCHAR skipW
[] = {'s','k','i','p','='};
1612 static const WCHAR tokensW
[] = {'t','o','k','e','n','s','='};
1613 static const WCHAR delimsW
[] = {'d','e','l','i','m','s','='};
1614 static const WCHAR usebackqW
[] = {'u','s','e','b','a','c','k','q'};
1615 static const WCHAR forf_defaultdelims
[] = {' ', '\t', '\0'};
1616 static const WCHAR forf_defaulttokens
[] = {'1', '\0'};
1618 /* Initialize to defaults */
1619 strcpyW(delims
, forf_defaultdelims
);
1620 strcpyW(tokens
, forf_defaulttokens
);
1625 /* Strip (optional) leading and trailing quotes */
1626 if ((*pos
== '"') && (pos
[len
-1] == '"')) {
1631 /* Process each keyword */
1632 while (pos
&& *pos
) {
1633 if (*pos
== ' ' || *pos
== '\t') {
1636 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1637 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1638 pos
, sizeof(eolW
)/sizeof(WCHAR
),
1639 eolW
, sizeof(eolW
)/sizeof(WCHAR
)) == CSTR_EQUAL
) {
1640 *eol
= *(pos
+ sizeof(eolW
)/sizeof(WCHAR
));
1641 pos
= pos
+ sizeof(eolW
)/sizeof(WCHAR
) + 1;
1642 WINE_TRACE("Found eol as %c(%x)\n", *eol
, *eol
);
1644 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1645 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1646 pos
, sizeof(skipW
)/sizeof(WCHAR
),
1647 skipW
, sizeof(skipW
)/sizeof(WCHAR
)) == CSTR_EQUAL
) {
1648 WCHAR
*nextchar
= NULL
;
1649 pos
= pos
+ sizeof(skipW
)/sizeof(WCHAR
);
1650 *skip
= strtoulW(pos
, &nextchar
, 0);
1651 WINE_TRACE("Found skip as %d lines\n", *skip
);
1654 /* Save if usebackq semantics are in effect */
1655 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1656 pos
, sizeof(usebackqW
)/sizeof(WCHAR
),
1657 usebackqW
, sizeof(usebackqW
)/sizeof(WCHAR
)) == CSTR_EQUAL
) {
1659 pos
= pos
+ sizeof(usebackqW
)/sizeof(WCHAR
);
1660 WINE_TRACE("Found usebackq\n");
1662 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1663 if you finish the optionsroot string with delims= otherwise the space is
1664 just a token delimiter! */
1665 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1666 pos
, sizeof(delimsW
)/sizeof(WCHAR
),
1667 delimsW
, sizeof(delimsW
)/sizeof(WCHAR
)) == CSTR_EQUAL
) {
1670 pos
= pos
+ sizeof(delimsW
)/sizeof(WCHAR
);
1671 while (*pos
&& *pos
!= ' ') {
1675 if (*pos
==' ' && *(pos
+1)==0) delims
[i
++] = *pos
;
1676 delims
[i
++] = 0; /* Null terminate the delims */
1677 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims
));
1679 /* Save the tokens being requested */
1680 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1681 pos
, sizeof(tokensW
)/sizeof(WCHAR
),
1682 tokensW
, sizeof(tokensW
)/sizeof(WCHAR
)) == CSTR_EQUAL
) {
1685 pos
= pos
+ sizeof(tokensW
)/sizeof(WCHAR
);
1686 while (*pos
&& *pos
!= ' ') {
1690 tokens
[i
++] = 0; /* Null terminate the tokens */
1691 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens
));
1694 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos
));
1701 /*****************************************************************************
1702 * WCMD_add_dirstowalk
1704 * When recursing through directories (for /r), we need to add to the list of
1705 * directories still to walk, any subdirectories of the one we are processing.
1708 * options [I] The remaining list of directories still to process
1710 * Note this routine inserts the subdirectories found between the entry being
1711 * processed, and any other directory still to be processed, mimicking what
1714 static void WCMD_add_dirstowalk(DIRECTORY_STACK
*dirsToWalk
) {
1715 DIRECTORY_STACK
*remainingDirs
= dirsToWalk
;
1716 WCHAR fullitem
[MAX_PATH
];
1717 WIN32_FIND_DATAW fd
;
1720 /* Build a generic search and add all directories on the list of directories
1722 strcpyW(fullitem
, dirsToWalk
->dirName
);
1723 strcatW(fullitem
, slashstarW
);
1724 hff
= FindFirstFileW(fullitem
, &fd
);
1725 if (hff
!= INVALID_HANDLE_VALUE
) {
1727 WINE_TRACE("Looking for subdirectories\n");
1728 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
1729 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
1730 (strcmpW(fd
.cFileName
, dotW
) != 0))
1732 /* Allocate memory, add to list */
1733 DIRECTORY_STACK
*toWalk
= heap_alloc(sizeof(DIRECTORY_STACK
));
1734 WINE_TRACE("(%p->%p)\n", remainingDirs
, remainingDirs
->next
);
1735 toWalk
->next
= remainingDirs
->next
;
1736 remainingDirs
->next
= toWalk
;
1737 remainingDirs
= toWalk
;
1738 toWalk
->dirName
= heap_alloc(sizeof(WCHAR
) * (strlenW(dirsToWalk
->dirName
) + 2 + strlenW(fd
.cFileName
)));
1739 strcpyW(toWalk
->dirName
, dirsToWalk
->dirName
);
1740 strcatW(toWalk
->dirName
, slashW
);
1741 strcatW(toWalk
->dirName
, fd
.cFileName
);
1742 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk
->dirName
),
1743 toWalk
, toWalk
->next
);
1745 } while (FindNextFileW(hff
, &fd
) != 0);
1746 WINE_TRACE("Finished adding all subdirectories\n");
1751 /**************************************************************************
1752 * WCMD_for_nexttoken
1754 * Parse the token= line, identifying the next highest number not processed
1755 * so far. Count how many tokens are referred (including duplicates) and
1756 * optionally return that, plus optionally indicate if the tokens= line
1760 * lasttoken [I] - Identifies the token index of the last one
1761 * returned so far (-1 used for first loop)
1762 * tokenstr [I] - The specified tokens= line
1763 * firstCmd [O] - Optionally indicate how many tokens are listed
1764 * doAll [O] - Optionally indicate if line ends with *
1765 * duplicates [O] - Optionally indicate if there is any evidence of
1766 * overlaying tokens in the string
1767 * Note the caller should keep a running track of duplicates as the tokens
1768 * are recursively passed. If any have duplicates, then the * token should
1771 static int WCMD_for_nexttoken(int lasttoken
, WCHAR
*tokenstr
,
1772 int *totalfound
, BOOL
*doall
,
1775 WCHAR
*pos
= tokenstr
;
1778 if (totalfound
) *totalfound
= 0;
1779 if (doall
) *doall
= FALSE
;
1780 if (duplicates
) *duplicates
= FALSE
;
1782 WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken
,
1783 wine_dbgstr_w(tokenstr
), nexttoken
);
1785 /* Loop through the token string, parsing it. Valid syntax is:
1786 token=m or x-y with comma delimiter and optionally * to finish*/
1788 int nextnumber1
, nextnumber2
= -1;
1791 /* Get the next number */
1792 nextnumber1
= strtoulW(pos
, &nextchar
, 10);
1794 /* If it is followed by a minus, it's a range, so get the next one as well */
1795 if (*nextchar
== '-') {
1796 nextnumber2
= strtoulW(nextchar
+1, &nextchar
, 10);
1798 /* We want to return the lowest number that is higher than lasttoken
1799 but only if range is positive */
1800 if (nextnumber2
>= nextnumber1
&&
1801 lasttoken
< nextnumber2
) {
1804 if (nexttoken
== -1) {
1805 nextvalue
= max(nextnumber1
, (lasttoken
+1));
1807 nextvalue
= min(nexttoken
, max(nextnumber1
, (lasttoken
+1)));
1810 /* Flag if duplicates identified */
1811 if (nexttoken
== nextvalue
&& duplicates
) *duplicates
= TRUE
;
1813 nexttoken
= nextvalue
;
1816 /* Update the running total for the whole range */
1817 if (nextnumber2
>= nextnumber1
&& totalfound
) {
1818 *totalfound
= *totalfound
+ 1 + (nextnumber2
- nextnumber1
);
1822 if (totalfound
) (*totalfound
)++;
1824 /* See if the number found is one we have already seen */
1825 if (nextnumber1
== nexttoken
&& duplicates
) *duplicates
= TRUE
;
1827 /* We want to return the lowest number that is higher than lasttoken */
1828 if (lasttoken
< nextnumber1
&&
1829 ((nexttoken
== -1) || (nextnumber1
< nexttoken
))) {
1830 nexttoken
= nextnumber1
;
1835 /* Remember if it is followed by a star, and if it is indicate a need to
1836 show all tokens, unless a duplicate has been found */
1837 if (*nextchar
== '*') {
1838 if (doall
) *doall
= TRUE
;
1839 if (totalfound
) (*totalfound
)++;
1842 /* Step on to the next character */
1848 if (nexttoken
== -1) nexttoken
= lasttoken
;
1849 WINE_TRACE("Found next token after %d was %d\n", lasttoken
, nexttoken
);
1850 if (totalfound
) WINE_TRACE("Found total tokens in total %d\n", *totalfound
);
1851 if (doall
&& *doall
) WINE_TRACE("Request for all tokens found\n");
1852 if (duplicates
&& *duplicates
) WINE_TRACE("Duplicate numbers found\n");
1856 /**************************************************************************
1859 * When parsing file or string contents (for /f), once the string to parse
1860 * has been identified, handle the various options and call the do part
1864 * cmdStart [I] - Identifies the list of commands making up the
1865 * for loop body (especially if brackets in use)
1866 * firstCmd [I] - The textual start of the command after the DO
1867 * which is within the first item of cmdStart
1868 * cmdEnd [O] - Identifies where to continue after the DO
1869 * variable [I] - The variable identified on the for line
1870 * buffer [I] - The string to parse
1871 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1872 * forf_skip [I/O] - How many lines to skip first
1873 * forf_eol [I] - The 'end of line' (comment) character
1874 * forf_delims [I] - The delimiters to use when breaking the string apart
1875 * forf_tokens [I] - The tokens to use when breaking the string apart
1877 static void WCMD_parse_line(CMD_LIST
*cmdStart
,
1878 const WCHAR
*firstCmd
,
1880 const WCHAR variable
,
1886 WCHAR
*forf_tokens
) {
1889 FOR_CONTEXT oldcontext
;
1890 int varidx
, varoffset
;
1891 int nexttoken
, lasttoken
= -1;
1892 BOOL starfound
= FALSE
;
1893 BOOL thisduplicate
= FALSE
;
1894 BOOL anyduplicates
= FALSE
;
1897 /* Skip lines if requested */
1903 /* Save away any existing for variable context (e.g. nested for loops) */
1904 oldcontext
= forloopcontext
;
1906 /* Extract the parameters based on the tokens= value (There will always
1907 be some value, as if it is not supplied, it defaults to tokens=1).
1909 Count how many tokens are named in the line, identify the lowest
1910 Empty (set to null terminated string) that number of named variables
1911 While lasttoken != nextlowest
1912 %letter = parameter number 'nextlowest'
1913 letter++ (if >26 or >52 abort)
1914 Go through token= string finding next lowest number
1915 If token ends in * set %letter = raw position of token(nextnumber+1)
1918 nexttoken
= WCMD_for_nexttoken(lasttoken
, forf_tokens
, &totalfound
,
1919 NULL
, &thisduplicate
);
1920 varidx
= FOR_VAR_IDX(variable
);
1922 /* Empty out variables */
1924 varidx
>= 0 && varoffset
<totalfound
&& ((varidx
+varoffset
)%26);
1926 forloopcontext
.variable
[varidx
+ varoffset
] = (WCHAR
*)nullW
;
1927 /* Stop if we walk beyond z or Z */
1928 if (((varidx
+varoffset
) % 26) == 0) break;
1931 /* Loop extracting the tokens */
1933 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer
));
1934 while (varidx
>= 0 && (nexttoken
> lasttoken
)) {
1935 anyduplicates
|= thisduplicate
;
1937 /* Extract the token number requested and set into the next variable context */
1938 parm
= WCMD_parameter_with_delims(buffer
, (nexttoken
-1), NULL
, FALSE
, FALSE
, forf_delims
);
1939 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken
,
1940 varidx
+ varoffset
, wine_dbgstr_w(parm
));
1942 forloopcontext
.variable
[varidx
+ varoffset
] = heap_strdupW(parm
);
1944 if (((varidx
+ varoffset
) %26) == 0) break;
1947 /* Find the next token */
1948 lasttoken
= nexttoken
;
1949 nexttoken
= WCMD_for_nexttoken(lasttoken
, forf_tokens
, NULL
,
1950 &starfound
, &thisduplicate
);
1953 /* If all the rest of the tokens were requested, and there is still space in
1954 the variable range, write them now */
1955 if (!anyduplicates
&& starfound
&& varidx
>= 0 && ((varidx
+varoffset
) % 26)) {
1957 WCMD_parameter_with_delims(buffer
, (nexttoken
-1), &parm
, FALSE
, FALSE
, forf_delims
);
1958 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
1959 varidx
+ varoffset
, wine_dbgstr_w(parm
));
1960 forloopcontext
.variable
[varidx
+ varoffset
] = heap_strdupW(parm
);
1963 /* Execute the body of the foor loop with these values */
1964 if (forloopcontext
.variable
[varidx
] && forloopcontext
.variable
[varidx
][0] != forf_eol
) {
1965 CMD_LIST
*thisCmdStart
= cmdStart
;
1967 WCMD_part_execute(&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
1968 *cmdEnd
= thisCmdStart
;
1971 /* Free the duplicated strings, and restore the context */
1974 for (i
=varidx
; i
<MAX_FOR_VARIABLES
; i
++) {
1975 if ((forloopcontext
.variable
[i
] != oldcontext
.variable
[i
]) &&
1976 (forloopcontext
.variable
[i
] != nullW
)) {
1977 heap_free(forloopcontext
.variable
[i
]);
1982 /* Restore the original for variable contextx */
1983 forloopcontext
= oldcontext
;
1986 /**************************************************************************
1987 * WCMD_forf_getinputhandle
1989 * Return a file handle which can be used for reading the input lines,
1990 * either to a specific file (which may be quote delimited as we have to
1991 * read the parameters in raw mode) or to a command which we need to
1992 * execute. The command being executed runs in its own shell and stores
1993 * its data in a temporary file.
1996 * usebackq [I] - Indicates whether usebackq is in effect or not
1997 * itemStr [I] - The item to be handled, either a filename or
1998 * whole command string to execute
1999 * iscmd [I] - Identifies whether this is a command or not
2001 * Returns a file handle which can be used to read the input lines from.
2003 static HANDLE
WCMD_forf_getinputhandle(BOOL usebackq
, WCHAR
*itemstr
, BOOL iscmd
) {
2004 WCHAR temp_str
[MAX_PATH
];
2005 WCHAR temp_file
[MAX_PATH
];
2006 WCHAR temp_cmd
[MAXSTRING
];
2007 HANDLE hinput
= INVALID_HANDLE_VALUE
;
2008 static const WCHAR redirOutW
[] = {'>','%','s','\0'};
2009 static const WCHAR cmdW
[] = {'C','M','D','\0'};
2010 static const WCHAR cmdslashcW
[] = {'C','M','D','.','E','X','E',' ',
2011 '/','C',' ','"','%','s','"','\0'};
2013 /* Remove leading and trailing character */
2014 if ((iscmd
&& (itemstr
[0] == '`' && usebackq
)) ||
2015 (iscmd
&& (itemstr
[0] == '\'' && !usebackq
)) ||
2016 (!iscmd
&& (itemstr
[0] == '"' && usebackq
)))
2018 itemstr
[strlenW(itemstr
)-1] = 0x00;
2023 /* Get temp filename */
2024 GetTempPathW(sizeof(temp_str
)/sizeof(WCHAR
), temp_str
);
2025 GetTempFileNameW(temp_str
, cmdW
, 0, temp_file
);
2027 /* Redirect output to the temporary file */
2028 wsprintfW(temp_str
, redirOutW
, temp_file
);
2029 wsprintfW(temp_cmd
, cmdslashcW
, itemstr
);
2030 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2031 wine_dbgstr_w(temp_cmd
), wine_dbgstr_w(temp_str
));
2032 WCMD_execute (temp_cmd
, temp_str
, NULL
, FALSE
);
2034 /* Open the file, read line by line and process */
2035 hinput
= CreateFileW(temp_file
, GENERIC_READ
, FILE_SHARE_READ
,
2036 NULL
, OPEN_EXISTING
, FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
2039 /* Open the file, read line by line and process */
2040 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr
));
2041 hinput
= CreateFileW(itemstr
, GENERIC_READ
, FILE_SHARE_READ
,
2042 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
2047 /**************************************************************************
2050 * Batch file loop processing.
2052 * On entry: cmdList contains the syntax up to the set
2053 * next cmdList and all in that bracket contain the set data
2054 * next cmdlist contains the DO cmd
2055 * following that is either brackets or && entries (as per if)
2059 void WCMD_for (WCHAR
*p
, CMD_LIST
**cmdList
) {
2061 WIN32_FIND_DATAW fd
;
2064 static const WCHAR inW
[] = {'i','n'};
2065 static const WCHAR doW
[] = {'d','o'};
2066 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
2069 WCHAR
*oldvariablevalue
;
2072 WCHAR optionsRoot
[MAX_PATH
];
2073 DIRECTORY_STACK
*dirsToWalk
= NULL
;
2074 BOOL expandDirs
= FALSE
;
2075 BOOL useNumbers
= FALSE
;
2076 BOOL doFileset
= FALSE
;
2077 BOOL doRecurse
= FALSE
;
2078 BOOL doExecuted
= FALSE
; /* Has the 'do' part been executed */
2079 LONG numbers
[3] = {0,0,0}; /* Defaults to 0 in native */
2081 CMD_LIST
*thisCmdStart
;
2082 int parameterNo
= 0;
2085 WCHAR forf_delims
[256];
2086 WCHAR forf_tokens
[MAXSTRING
];
2087 BOOL forf_usebackq
= FALSE
;
2089 /* Handle optional qualifiers (multiple are allowed) */
2090 WCHAR
*thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2093 while (thisArg
&& *thisArg
== '/') {
2094 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg
));
2096 switch (toupperW(*thisArg
)) {
2097 case 'D': expandDirs
= TRUE
; break;
2098 case 'L': useNumbers
= TRUE
; break;
2100 /* Recursive is special case - /R can have an optional path following it */
2101 /* filenamesets are another special case - /F can have an optional options following it */
2105 /* When recursing directories, use current directory as the starting point unless
2106 subsequently overridden */
2107 doRecurse
= (toupperW(*thisArg
) == 'R');
2108 if (doRecurse
) GetCurrentDirectoryW(sizeof(optionsRoot
)/sizeof(WCHAR
), optionsRoot
);
2110 doFileset
= (toupperW(*thisArg
) == 'F');
2112 /* Retrieve next parameter to see if is root/options (raw form required
2113 with for /f, or unquoted in for /r) */
2114 thisArg
= WCMD_parameter(p
, parameterNo
, NULL
, doFileset
, FALSE
);
2116 /* Next parm is either qualifier, path/options or variable -
2117 only care about it if it is the path/options */
2118 if (thisArg
&& *thisArg
!= '/' && *thisArg
!= '%') {
2120 strcpyW(optionsRoot
, thisArg
);
2125 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg
);
2128 /* Step to next token */
2129 thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2132 /* Ensure line continues with variable */
2133 if (!*thisArg
|| *thisArg
!= '%') {
2134 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2138 /* With for /f parse the options if provided */
2140 if (!WCMD_parse_forf_options(optionsRoot
, &forf_eol
, &forf_skip
,
2141 forf_delims
, forf_tokens
, &forf_usebackq
))
2143 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2147 /* Set up the list of directories to recurse if we are going to */
2148 } else if (doRecurse
) {
2149 /* Allocate memory, add to list */
2150 dirsToWalk
= heap_alloc(sizeof(DIRECTORY_STACK
));
2151 dirsToWalk
->next
= NULL
;
2152 dirsToWalk
->dirName
= heap_strdupW(optionsRoot
);
2153 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk
->dirName
));
2156 /* Variable should follow */
2157 strcpyW(variable
, thisArg
);
2158 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable
));
2159 varidx
= FOR_VAR_IDX(variable
[1]);
2161 /* Ensure line continues with IN */
2162 thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2164 || !(CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2165 thisArg
, sizeof(inW
)/sizeof(inW
[0]), inW
,
2166 sizeof(inW
)/sizeof(inW
[0])) == CSTR_EQUAL
)) {
2167 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2171 /* Save away where the set of data starts and the variable */
2172 thisDepth
= (*cmdList
)->bracketDepth
;
2173 *cmdList
= (*cmdList
)->nextcommand
;
2174 setStart
= (*cmdList
);
2176 /* Skip until the close bracket */
2177 WINE_TRACE("Searching %p as the set\n", *cmdList
);
2179 (*cmdList
)->command
!= NULL
&&
2180 (*cmdList
)->bracketDepth
> thisDepth
) {
2181 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList
);
2182 *cmdList
= (*cmdList
)->nextcommand
;
2185 /* Skip the close bracket, if there is one */
2186 if (*cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
2188 /* Syntax error if missing close bracket, or nothing following it
2189 and once we have the complete set, we expect a DO */
2190 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList
);
2191 if ((*cmdList
== NULL
)
2192 || !WCMD_keyword_ws_found(doW
, sizeof(doW
)/sizeof(doW
[0]), (*cmdList
)->command
)) {
2194 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2200 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2201 mode, or once for the rest of the time. */
2204 /* Save away the starting position for the commands (and offset for the
2206 cmdStart
= *cmdList
;
2207 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
2210 /* If we are recursing directories (ie /R), add all sub directories now, then
2211 prefix the root when searching for the item */
2212 if (dirsToWalk
) WCMD_add_dirstowalk(dirsToWalk
);
2215 /* Loop through all set entries */
2217 thisSet
->command
!= NULL
&&
2218 thisSet
->bracketDepth
>= thisDepth
) {
2220 /* Loop through all entries on the same line */
2223 WCHAR buffer
[MAXSTRING
];
2225 WINE_TRACE("Processing for set %p\n", thisSet
);
2227 while (*(item
= WCMD_parameter (thisSet
->command
, i
, &itemStart
, TRUE
, FALSE
))) {
2230 * If the parameter within the set has a wildcard then search for matching files
2231 * otherwise do a literal substitution.
2233 static const WCHAR wildcards
[] = {'*','?','\0'};
2234 thisCmdStart
= cmdStart
;
2237 WINE_TRACE("Processing for item %d '%s'\n", itemNum
, wine_dbgstr_w(item
));
2239 if (!useNumbers
&& !doFileset
) {
2240 WCHAR fullitem
[MAX_PATH
];
2243 /* Now build the item to use / search for in the specified directory,
2244 as it is fully qualified in the /R case */
2246 strcpyW(fullitem
, dirsToWalk
->dirName
);
2247 strcatW(fullitem
, slashW
);
2248 strcatW(fullitem
, item
);
2250 WCHAR
*prefix
= strrchrW(item
, '\\');
2251 if (prefix
) prefixlen
= (prefix
- item
) + 1;
2252 strcpyW(fullitem
, item
);
2255 if (strpbrkW (fullitem
, wildcards
)) {
2256 hff
= FindFirstFileW(fullitem
, &fd
);
2257 if (hff
!= INVALID_HANDLE_VALUE
) {
2259 BOOL isDirectory
= FALSE
;
2261 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) isDirectory
= TRUE
;
2263 /* Handle as files or dirs appropriately, but ignore . and .. */
2264 if (isDirectory
== expandDirs
&&
2265 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
2266 (strcmpW(fd
.cFileName
, dotW
) != 0))
2268 thisCmdStart
= cmdStart
;
2269 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd
.cFileName
));
2272 strcpyW(fullitem
, dirsToWalk
->dirName
);
2273 strcatW(fullitem
, slashW
);
2274 strcatW(fullitem
, fd
.cFileName
);
2276 if (prefixlen
) lstrcpynW(fullitem
, item
, prefixlen
+ 1);
2277 fullitem
[prefixlen
] = 0x00;
2278 strcatW(fullitem
, fd
.cFileName
);
2282 /* Save away any existing for variable context (e.g. nested for loops)
2283 and restore it after executing the body of this for loop */
2285 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2286 forloopcontext
.variable
[varidx
] = fullitem
;
2288 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2289 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2291 cmdEnd
= thisCmdStart
;
2293 } while (FindNextFileW(hff
, &fd
) != 0);
2299 /* Save away any existing for variable context (e.g. nested for loops)
2300 and restore it after executing the body of this for loop */
2302 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2303 forloopcontext
.variable
[varidx
] = fullitem
;
2305 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2306 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2308 cmdEnd
= thisCmdStart
;
2311 } else if (useNumbers
) {
2312 /* Convert the first 3 numbers to signed longs and save */
2313 if (itemNum
<=3) numbers
[itemNum
-1] = atolW(item
);
2314 /* else ignore them! */
2316 /* Filesets - either a list of files, or a command to run and parse the output */
2317 } else if (doFileset
&& ((!forf_usebackq
&& *itemStart
!= '"') ||
2318 (forf_usebackq
&& *itemStart
!= '\''))) {
2323 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum
,
2324 wine_dbgstr_w(item
));
2326 /* If backquote or single quote, we need to launch that command
2327 and parse the results - use a temporary file */
2328 if ((forf_usebackq
&& *itemStart
== '`') ||
2329 (!forf_usebackq
&& *itemStart
== '\'')) {
2331 /* Use itemstart because the command is the whole set, not just the first token */
2332 itemparm
= itemStart
;
2335 /* Use item because the file to process is just the first item in the set */
2338 input
= WCMD_forf_getinputhandle(forf_usebackq
, itemparm
, (itemparm
==itemStart
));
2340 /* Process the input file */
2341 if (input
== INVALID_HANDLE_VALUE
) {
2342 WCMD_print_error ();
2343 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), item
);
2345 return; /* FOR loop aborts at first failure here */
2349 /* Read line by line until end of file */
2350 while (WCMD_fgets(buffer
, sizeof(buffer
)/sizeof(WCHAR
), input
)) {
2351 WCMD_parse_line(cmdStart
, firstCmd
, &cmdEnd
, variable
[1], buffer
, &doExecuted
,
2352 &forf_skip
, forf_eol
, forf_delims
, forf_tokens
);
2355 CloseHandle (input
);
2358 /* When we have processed the item as a whole command, abort future set processing */
2359 if (itemparm
==itemStart
) {
2364 /* Filesets - A string literal */
2365 } else if (doFileset
&& ((!forf_usebackq
&& *itemStart
== '"') ||
2366 (forf_usebackq
&& *itemStart
== '\''))) {
2368 /* Remove leading and trailing character, ready to parse with delims= delimiters
2369 Note that the last quote is removed from the set and the string terminates
2370 there to mimic windows */
2371 WCHAR
*strend
= strrchrW(itemStart
, forf_usebackq
?'\'':'"');
2377 /* Copy the item away from the global buffer used by WCMD_parameter */
2378 strcpyW(buffer
, itemStart
);
2379 WCMD_parse_line(cmdStart
, firstCmd
, &cmdEnd
, variable
[1], buffer
, &doExecuted
,
2380 &forf_skip
, forf_eol
, forf_delims
, forf_tokens
);
2382 /* Only one string can be supplied in the whole set, abort future set processing */
2387 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
2391 /* Move onto the next set line */
2392 if (thisSet
) thisSet
= thisSet
->nextcommand
;
2395 /* If /L is provided, now run the for loop */
2398 static const WCHAR fmt
[] = {'%','d','\0'};
2400 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2401 numbers
[0], numbers
[2], numbers
[1]);
2403 (numbers
[1]<0)? i
>=numbers
[2] : i
<=numbers
[2];
2406 sprintfW(thisNum
, fmt
, i
);
2407 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum
));
2409 thisCmdStart
= cmdStart
;
2412 /* Save away any existing for variable context (e.g. nested for loops)
2413 and restore it after executing the body of this for loop */
2415 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2416 forloopcontext
.variable
[varidx
] = thisNum
;
2418 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2419 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2421 cmdEnd
= thisCmdStart
;
2424 /* If we are walking directories, move on to any which remain */
2425 if (dirsToWalk
!= NULL
) {
2426 DIRECTORY_STACK
*nextDir
= dirsToWalk
->next
;
2427 heap_free(dirsToWalk
->dirName
);
2428 heap_free(dirsToWalk
);
2429 dirsToWalk
= nextDir
;
2430 if (dirsToWalk
) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2431 wine_dbgstr_w(dirsToWalk
->dirName
));
2432 else WINE_TRACE("Finished all directories.\n");
2435 } while (dirsToWalk
!= NULL
);
2437 /* Now skip over the do part if we did not perform the for loop so far.
2438 We store in cmdEnd the next command after the do block, but we only
2439 know this if something was run. If it has not been, we need to calculate
2442 thisCmdStart
= cmdStart
;
2443 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2444 WCMD_part_execute(&thisCmdStart
, firstCmd
, FALSE
, FALSE
);
2445 cmdEnd
= thisCmdStart
;
2448 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2449 all processing, OR it should be pointing to the end of && processing OR
2450 it should be pointing at the NULL end of bracket for the DO. The return
2451 value needs to be the NEXT command to execute, which it either is, or
2452 we need to step over the closing bracket */
2454 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
2457 /**************************************************************************
2460 * Simple on-line help. Help text is stored in the resource file.
2463 void WCMD_give_help (const WCHAR
*args
)
2467 args
= WCMD_skip_leading_spaces((WCHAR
*) args
);
2468 if (strlenW(args
) == 0) {
2469 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
2472 /* Display help message for builtin commands */
2473 for (i
=0; i
<=WCMD_EXIT
; i
++) {
2474 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2475 args
, -1, inbuilt
[i
], -1) == CSTR_EQUAL
) {
2476 WCMD_output_asis (WCMD_LoadMessage(i
));
2480 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2481 for (i
= 0; i
<= (sizeof(externals
)/sizeof(externals
[0])); i
++) {
2482 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2483 args
, -1, externals
[i
], -1) == CSTR_EQUAL
) {
2485 static const WCHAR helpW
[] = {' ', '/','?','\0'};
2487 strcatW(cmd
, helpW
);
2488 WCMD_run_program(cmd
, FALSE
);
2492 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), args
);
2497 /****************************************************************************
2500 * Batch file jump instruction. Not the most efficient algorithm ;-)
2501 * Prints error message if the specified label cannot be found - the file pointer is
2502 * then at EOF, effectively stopping the batch file.
2503 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2506 void WCMD_goto (CMD_LIST
**cmdList
) {
2508 WCHAR string
[MAX_PATH
];
2509 WCHAR
*labelend
= NULL
;
2510 const WCHAR labelEndsW
[] = {'>','<','|','&',' ',':','\t','\0'};
2512 /* Do not process any more parts of a processed multipart or multilines command */
2513 if (cmdList
) *cmdList
= NULL
;
2515 if (context
!= NULL
) {
2516 WCHAR
*paramStart
= param1
, *str
;
2517 static const WCHAR eofW
[] = {':','e','o','f','\0'};
2519 if (param1
[0] == 0x00) {
2520 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2524 /* Handle special :EOF label */
2525 if (lstrcmpiW (eofW
, param1
) == 0) {
2526 context
-> skip_rest
= TRUE
;
2530 /* Support goto :label as well as goto label plus remove trailing chars */
2531 if (*paramStart
== ':') paramStart
++;
2532 labelend
= strpbrkW(paramStart
, labelEndsW
);
2533 if (labelend
) *labelend
= 0x00;
2534 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart
));
2536 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
2537 while (*paramStart
&&
2538 WCMD_fgets (string
, sizeof(string
)/sizeof(WCHAR
), context
-> h
)) {
2541 /* Ignore leading whitespace or no-echo character */
2542 while (*str
=='@' || isspaceW (*str
)) str
++;
2544 /* If the first real character is a : then this is a label */
2548 /* Skip spaces between : and label */
2549 while (isspaceW (*str
)) str
++;
2550 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str
));
2552 /* Label ends at whitespace or redirection characters */
2553 labelend
= strpbrkW(str
, labelEndsW
);
2554 if (labelend
) *labelend
= 0x00;
2555 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str
));
2557 if (lstrcmpiW (str
, paramStart
) == 0) return;
2560 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET
));
2561 context
-> skip_rest
= TRUE
;
2566 /*****************************************************************************
2569 * Push a directory onto the stack
2572 void WCMD_pushd (const WCHAR
*args
)
2574 struct env_stack
*curdir
;
2576 static const WCHAR parmD
[] = {'/','D','\0'};
2578 if (strchrW(args
, '/') != NULL
) {
2579 SetLastError(ERROR_INVALID_PARAMETER
);
2584 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
2585 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
2586 if( !curdir
|| !thisdir
) {
2589 WINE_ERR ("out of memory\n");
2593 /* Change directory using CD code with /D parameter */
2594 strcpyW(quals
, parmD
);
2595 GetCurrentDirectoryW (1024, thisdir
);
2597 WCMD_setshow_default(args
);
2603 curdir
-> next
= pushd_directories
;
2604 curdir
-> strings
= thisdir
;
2605 if (pushd_directories
== NULL
) {
2606 curdir
-> u
.stackdepth
= 1;
2608 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
2610 pushd_directories
= curdir
;
2615 /*****************************************************************************
2618 * Pop a directory from the stack
2621 void WCMD_popd (void) {
2622 struct env_stack
*temp
= pushd_directories
;
2624 if (!pushd_directories
)
2627 /* pop the old environment from the stack, and make it the current dir */
2628 pushd_directories
= temp
->next
;
2629 SetCurrentDirectoryW(temp
->strings
);
2630 LocalFree (temp
->strings
);
2634 /*******************************************************************
2635 * evaluate_if_comparison
2637 * Evaluates an "if" comparison operation
2640 * leftOperand [I] left operand, non NULL
2641 * operator [I] "if" binary comparison operator, non NULL
2642 * rightOperand [I] right operand, non NULL
2643 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2646 * Success: 1 if operator applied to the operands evaluates to TRUE
2647 * 0 if operator applied to the operands evaluates to FALSE
2648 * Failure: -1 if operator is not recognized
2650 static int evaluate_if_comparison(const WCHAR
*leftOperand
, const WCHAR
*operator,
2651 const WCHAR
*rightOperand
, int caseInsensitive
)
2653 WCHAR
*endptr_leftOp
, *endptr_rightOp
;
2654 long int leftOperand_int
, rightOperand_int
;
2656 static const WCHAR lssW
[] = {'l','s','s','\0'};
2657 static const WCHAR leqW
[] = {'l','e','q','\0'};
2658 static const WCHAR equW
[] = {'e','q','u','\0'};
2659 static const WCHAR neqW
[] = {'n','e','q','\0'};
2660 static const WCHAR geqW
[] = {'g','e','q','\0'};
2661 static const WCHAR gtrW
[] = {'g','t','r','\0'};
2663 /* == is a special case, as it always compares strings */
2664 if (!lstrcmpiW(operator, eqeqW
))
2665 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) == 0
2666 : lstrcmpW (leftOperand
, rightOperand
) == 0;
2668 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2669 leftOperand_int
= strtolW(leftOperand
, &endptr_leftOp
, 0);
2670 rightOperand_int
= strtolW(rightOperand
, &endptr_rightOp
, 0);
2671 int_operands
= (!*endptr_leftOp
) && (!*endptr_rightOp
);
2673 /* Perform actual (integer or string) comparison */
2674 if (!lstrcmpiW(operator, lssW
)) {
2676 return leftOperand_int
< rightOperand_int
;
2678 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) < 0
2679 : lstrcmpW (leftOperand
, rightOperand
) < 0;
2682 if (!lstrcmpiW(operator, leqW
)) {
2684 return leftOperand_int
<= rightOperand_int
;
2686 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) <= 0
2687 : lstrcmpW (leftOperand
, rightOperand
) <= 0;
2690 if (!lstrcmpiW(operator, equW
)) {
2692 return leftOperand_int
== rightOperand_int
;
2694 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) == 0
2695 : lstrcmpW (leftOperand
, rightOperand
) == 0;
2698 if (!lstrcmpiW(operator, neqW
)) {
2700 return leftOperand_int
!= rightOperand_int
;
2702 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) != 0
2703 : lstrcmpW (leftOperand
, rightOperand
) != 0;
2706 if (!lstrcmpiW(operator, geqW
)) {
2708 return leftOperand_int
>= rightOperand_int
;
2710 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) >= 0
2711 : lstrcmpW (leftOperand
, rightOperand
) >= 0;
2714 if (!lstrcmpiW(operator, gtrW
)) {
2716 return leftOperand_int
> rightOperand_int
;
2718 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) > 0
2719 : lstrcmpW (leftOperand
, rightOperand
) > 0;
2725 /****************************************************************************
2728 * Batch file conditional.
2730 * On entry, cmdlist will point to command containing the IF, and optionally
2731 * the first command to execute (if brackets not found)
2732 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2733 * If ('s were found, execute all within that bracket
2734 * Command may optionally be followed by an ELSE - need to skip instructions
2735 * in the else using the same logic
2737 * FIXME: Much more syntax checking needed!
2739 void WCMD_if (WCHAR
*p
, CMD_LIST
**cmdList
)
2741 int negate
; /* Negate condition */
2742 int test
; /* Condition evaluation result */
2743 WCHAR condition
[MAX_PATH
], *command
;
2744 static const WCHAR notW
[] = {'n','o','t','\0'};
2745 static const WCHAR errlvlW
[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2746 static const WCHAR existW
[] = {'e','x','i','s','t','\0'};
2747 static const WCHAR defdW
[] = {'d','e','f','i','n','e','d','\0'};
2748 static const WCHAR parmI
[] = {'/','I','\0'};
2749 int caseInsensitive
= (strstrW(quals
, parmI
) != NULL
);
2751 negate
= !lstrcmpiW(param1
,notW
);
2752 strcpyW(condition
, (negate
? param2
: param1
));
2753 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition
));
2755 if (!lstrcmpiW (condition
, errlvlW
)) {
2756 WCHAR
*param
= WCMD_parameter(p
, 1+negate
, NULL
, FALSE
, FALSE
);
2758 long int param_int
= strtolW(param
, &endptr
, 10);
2759 if (*endptr
) goto syntax_err
;
2760 test
= ((long int)errorlevel
>= param_int
);
2761 WCMD_parameter(p
, 2+negate
, &command
, FALSE
, FALSE
);
2763 else if (!lstrcmpiW (condition
, existW
)) {
2764 test
= (GetFileAttributesW(WCMD_parameter(p
, 1+negate
, NULL
, FALSE
, FALSE
))
2765 != INVALID_FILE_ATTRIBUTES
);
2766 WCMD_parameter(p
, 2+negate
, &command
, FALSE
, FALSE
);
2768 else if (!lstrcmpiW (condition
, defdW
)) {
2769 test
= (GetEnvironmentVariableW(WCMD_parameter(p
, 1+negate
, NULL
, FALSE
, FALSE
),
2771 WCMD_parameter(p
, 2+negate
, &command
, FALSE
, FALSE
);
2773 else { /* comparison operation */
2774 WCHAR leftOperand
[MAXSTRING
], rightOperand
[MAXSTRING
], operator[MAXSTRING
];
2777 strcpyW(leftOperand
, WCMD_parameter(p
, negate
+caseInsensitive
, ¶mStart
, TRUE
, FALSE
));
2781 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2782 p
= paramStart
+ strlenW(leftOperand
);
2783 while (*p
== ' ' || *p
== '\t')
2786 if (!strncmpW(p
, eqeqW
, strlenW(eqeqW
)))
2787 strcpyW(operator, eqeqW
);
2789 strcpyW(operator, WCMD_parameter(p
, 0, ¶mStart
, FALSE
, FALSE
));
2790 if (!*operator) goto syntax_err
;
2792 p
+= strlenW(operator);
2794 strcpyW(rightOperand
, WCMD_parameter(p
, 0, ¶mStart
, TRUE
, FALSE
));
2798 test
= evaluate_if_comparison(leftOperand
, operator, rightOperand
, caseInsensitive
);
2802 p
= paramStart
+ strlenW(rightOperand
);
2803 WCMD_parameter(p
, 0, &command
, FALSE
, FALSE
);
2806 /* Process rest of IF statement which is on the same line
2807 Note: This may process all or some of the cmdList (eg a GOTO) */
2808 WCMD_part_execute(cmdList
, command
, TRUE
, (test
!= negate
));
2812 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
2815 /****************************************************************************
2818 * Move a file, directory tree or wildcarded set of files.
2821 void WCMD_move (void)
2824 WIN32_FIND_DATAW fd
;
2826 WCHAR input
[MAX_PATH
];
2827 WCHAR output
[MAX_PATH
];
2829 WCHAR dir
[MAX_PATH
];
2830 WCHAR fname
[MAX_PATH
];
2831 WCHAR ext
[MAX_PATH
];
2833 if (param1
[0] == 0x00) {
2834 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2838 /* If no destination supplied, assume current directory */
2839 if (param2
[0] == 0x00) {
2840 strcpyW(param2
, dotW
);
2843 /* If 2nd parm is directory, then use original filename */
2844 /* Convert partial path to full path */
2845 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
2846 GetFullPathNameW(param2
, sizeof(output
)/sizeof(WCHAR
), output
, NULL
);
2847 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
2848 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
2850 /* Split into components */
2851 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
2853 hff
= FindFirstFileW(input
, &fd
);
2854 if (hff
== INVALID_HANDLE_VALUE
)
2858 WCHAR dest
[MAX_PATH
];
2859 WCHAR src
[MAX_PATH
];
2863 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
2865 /* Build src & dest name */
2866 strcpyW(src
, drive
);
2869 /* See if dest is an existing directory */
2870 attribs
= GetFileAttributesW(output
);
2871 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
2872 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
2873 strcpyW(dest
, output
);
2874 strcatW(dest
, slashW
);
2875 strcatW(dest
, fd
.cFileName
);
2877 strcpyW(dest
, output
);
2880 strcatW(src
, fd
.cFileName
);
2882 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
2883 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
2885 /* If destination exists, prompt unless /Y supplied */
2886 if (GetFileAttributesW(dest
) != INVALID_FILE_ATTRIBUTES
) {
2888 WCHAR copycmd
[MAXSTRING
];
2891 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2892 if (strstrW (quals
, parmNoY
))
2894 else if (strstrW (quals
, parmY
))
2897 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
2898 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
2899 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))
2900 && ! lstrcmpiW (copycmd
, parmY
));
2903 /* Prompt if overwriting */
2907 /* Ask for confirmation */
2908 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
2909 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
2910 LocalFree(question
);
2912 /* So delete the destination prior to the move */
2914 if (!DeleteFileW(dest
)) {
2915 WCMD_print_error ();
2924 status
= MoveFileW(src
, dest
);
2930 WCMD_print_error ();
2933 } while (FindNextFileW(hff
, &fd
) != 0);
2938 /****************************************************************************
2941 * Suspend execution of a batch script until a key is typed
2944 void WCMD_pause (void)
2950 HANDLE hIn
= GetStdHandle(STD_INPUT_HANDLE
);
2952 have_console
= GetConsoleMode(hIn
, &oldmode
);
2954 SetConsoleMode(hIn
, 0);
2956 WCMD_output_asis(anykey
);
2957 WCMD_ReadFile(hIn
, &key
, 1, &count
);
2959 SetConsoleMode(hIn
, oldmode
);
2962 /****************************************************************************
2965 * Delete a directory.
2968 void WCMD_remove_dir (WCHAR
*args
) {
2971 int argsProcessed
= 0;
2973 static const WCHAR parmS
[] = {'/','S','\0'};
2974 static const WCHAR parmQ
[] = {'/','Q','\0'};
2976 /* Loop through all args */
2978 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
2979 if (argN
&& argN
[0] != '/') {
2980 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
2981 wine_dbgstr_w(quals
));
2984 /* If subdirectory search not supplied, just try to remove
2985 and report error if it fails (eg if it contains a file) */
2986 if (strstrW (quals
, parmS
) == NULL
) {
2987 if (!RemoveDirectoryW(thisArg
)) WCMD_print_error ();
2989 /* Otherwise use ShFileOp to recursively remove a directory */
2992 SHFILEOPSTRUCTW lpDir
;
2995 if (strstrW (quals
, parmQ
) == NULL
) {
2997 WCHAR question
[MAXSTRING
];
2998 static const WCHAR fmt
[] = {'%','s',' ','\0'};
3000 /* Ask for confirmation */
3001 wsprintfW(question
, fmt
, thisArg
);
3002 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
3004 /* Abort if answer is 'N' */
3011 lpDir
.pFrom
= thisArg
;
3012 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
3013 lpDir
.wFunc
= FO_DELETE
;
3015 /* SHFileOperationW needs file list with a double null termination */
3016 thisArg
[lstrlenW(thisArg
) + 1] = 0x00;
3018 if (SHFileOperationW(&lpDir
)) WCMD_print_error ();
3023 /* Handle no valid args */
3024 if (argsProcessed
== 0) {
3025 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
3031 /****************************************************************************
3037 void WCMD_rename (void)
3041 WIN32_FIND_DATAW fd
;
3042 WCHAR input
[MAX_PATH
];
3043 WCHAR
*dotDst
= NULL
;
3045 WCHAR dir
[MAX_PATH
];
3046 WCHAR fname
[MAX_PATH
];
3047 WCHAR ext
[MAX_PATH
];
3051 /* Must be at least two args */
3052 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
3053 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
3058 /* Destination cannot contain a drive letter or directory separator */
3059 if ((strchrW(param2
,':') != NULL
) || (strchrW(param2
,'\\') != NULL
)) {
3060 SetLastError(ERROR_INVALID_PARAMETER
);
3066 /* Convert partial path to full path */
3067 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
3068 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
3069 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
3070 dotDst
= strchrW(param2
, '.');
3072 /* Split into components */
3073 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
3075 hff
= FindFirstFileW(input
, &fd
);
3076 if (hff
== INVALID_HANDLE_VALUE
)
3080 WCHAR dest
[MAX_PATH
];
3081 WCHAR src
[MAX_PATH
];
3082 WCHAR
*dotSrc
= NULL
;
3085 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
3087 /* FIXME: If dest name or extension is *, replace with filename/ext
3088 part otherwise use supplied name. This supports:
3090 ren jim.* fred.* etc
3091 However, windows has a more complex algorithm supporting eg
3092 ?'s and *'s mid name */
3093 dotSrc
= strchrW(fd
.cFileName
, '.');
3095 /* Build src & dest name */
3096 strcpyW(src
, drive
);
3099 dirLen
= strlenW(src
);
3100 strcatW(src
, fd
.cFileName
);
3103 if (param2
[0] == '*') {
3104 strcatW(dest
, fd
.cFileName
);
3105 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
3107 strcatW(dest
, param2
);
3108 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
3111 /* Build Extension */
3112 if (dotDst
&& (*(dotDst
+1)=='*')) {
3113 if (dotSrc
) strcatW(dest
, dotSrc
);
3114 } else if (dotDst
) {
3115 strcatW(dest
, dotDst
);
3118 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
3119 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
3121 status
= MoveFileW(src
, dest
);
3124 WCMD_print_error ();
3127 } while (FindNextFileW(hff
, &fd
) != 0);
3132 /*****************************************************************************
3135 * Make a copy of the environment.
3137 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
3147 len
+= (strlenW(&env
[len
]) + 1);
3149 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
3152 WINE_ERR("out of memory\n");
3155 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
3161 /*****************************************************************************
3164 * setlocal pushes the environment onto a stack
3165 * Save the environment as unicode so we don't screw anything up.
3167 void WCMD_setlocal (const WCHAR
*s
) {
3169 struct env_stack
*env_copy
;
3170 WCHAR cwd
[MAX_PATH
];
3172 static const WCHAR ondelayW
[] = {'E','N','A','B','L','E','D','E','L','A',
3173 'Y','E','D','E','X','P','A','N','S','I',
3175 static const WCHAR offdelayW
[] = {'D','I','S','A','B','L','E','D','E','L',
3176 'A','Y','E','D','E','X','P','A','N','S',
3179 /* setlocal does nothing outside of batch programs */
3180 if (!context
) return;
3182 /* DISABLEEXTENSIONS ignored */
3184 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3185 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3186 if (!strcmpiW(param1
, ondelayW
) || !strcmpiW(param2
, ondelayW
)) {
3188 } else if (!strcmpiW(param1
, offdelayW
) || !strcmpiW(param2
, offdelayW
)) {
3191 newdelay
= delayedsubst
;
3193 WINE_TRACE("Setting delayed expansion to %d\n", newdelay
);
3195 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
3198 WINE_ERR ("out of memory\n");
3202 env
= GetEnvironmentStringsW ();
3203 env_copy
->strings
= WCMD_dupenv (env
);
3204 if (env_copy
->strings
)
3206 env_copy
->batchhandle
= context
->h
;
3207 env_copy
->next
= saved_environment
;
3208 env_copy
->delayedsubst
= delayedsubst
;
3209 delayedsubst
= newdelay
;
3210 saved_environment
= env_copy
;
3212 /* Save the current drive letter */
3213 GetCurrentDirectoryW(MAX_PATH
, cwd
);
3214 env_copy
->u
.cwd
= cwd
[0];
3217 LocalFree (env_copy
);
3219 FreeEnvironmentStringsW (env
);
3223 /*****************************************************************************
3226 * endlocal pops the environment off a stack
3227 * Note: When searching for '=', search from WCHAR position 1, to handle
3228 * special internal environment variables =C:, =D: etc
3230 void WCMD_endlocal (void) {
3231 WCHAR
*env
, *old
, *p
;
3232 struct env_stack
*temp
;
3235 /* setlocal does nothing outside of batch programs */
3236 if (!context
) return;
3238 /* setlocal needs a saved environment from within the same context (batch
3239 program) as it was saved in */
3240 if (!saved_environment
|| saved_environment
->batchhandle
!= context
->h
)
3243 /* pop the old environment from the stack */
3244 temp
= saved_environment
;
3245 saved_environment
= temp
->next
;
3247 /* delete the current environment, totally */
3248 env
= GetEnvironmentStringsW ();
3249 old
= WCMD_dupenv (env
);
3252 n
= strlenW(&old
[len
]) + 1;
3253 p
= strchrW(&old
[len
] + 1, '=');
3257 SetEnvironmentVariableW (&old
[len
], NULL
);
3262 FreeEnvironmentStringsW (env
);
3264 /* restore old environment */
3265 env
= temp
->strings
;
3267 delayedsubst
= temp
->delayedsubst
;
3268 WINE_TRACE("Delayed expansion now %d\n", delayedsubst
);
3270 n
= strlenW(&env
[len
]) + 1;
3271 p
= strchrW(&env
[len
] + 1, '=');
3275 SetEnvironmentVariableW (&env
[len
], p
);
3280 /* Restore current drive letter */
3281 if (IsCharAlphaW(temp
->u
.cwd
)) {
3283 WCHAR cwd
[MAX_PATH
];
3284 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
3286 wsprintfW(envvar
, fmt
, temp
->u
.cwd
);
3287 if (GetEnvironmentVariableW(envvar
, cwd
, MAX_PATH
)) {
3288 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
3289 SetCurrentDirectoryW(cwd
);
3297 /*****************************************************************************
3298 * WCMD_setshow_default
3300 * Set/Show the current default directory
3303 void WCMD_setshow_default (const WCHAR
*args
) {
3309 WIN32_FIND_DATAW fd
;
3311 static const WCHAR parmD
[] = {'/','D','\0'};
3313 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args
));
3315 /* Skip /D and trailing whitespace if on the front of the command line */
3316 if (CompareStringW(LOCALE_USER_DEFAULT
,
3317 NORM_IGNORECASE
| SORT_STRINGSORT
,
3318 args
, 2, parmD
, -1) == CSTR_EQUAL
) {
3320 while (*args
&& (*args
==' ' || *args
=='\t'))
3324 GetCurrentDirectoryW(sizeof(cwd
)/sizeof(WCHAR
), cwd
);
3325 if (strlenW(args
) == 0) {
3326 strcatW (cwd
, newlineW
);
3327 WCMD_output_asis (cwd
);
3330 /* Remove any double quotes, which may be in the
3331 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3334 if (*args
!= '"') *pos
++ = *args
;
3337 while (pos
> string
&& (*(pos
-1) == ' ' || *(pos
-1) == '\t'))
3341 /* Search for appropriate directory */
3342 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
3343 hff
= FindFirstFileW(string
, &fd
);
3344 if (hff
!= INVALID_HANDLE_VALUE
) {
3346 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
3347 WCHAR fpath
[MAX_PATH
];
3349 WCHAR dir
[MAX_PATH
];
3350 WCHAR fname
[MAX_PATH
];
3351 WCHAR ext
[MAX_PATH
];
3352 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
3354 /* Convert path into actual directory spec */
3355 GetFullPathNameW(string
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
3356 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
3359 wsprintfW(string
, fmt
, drive
, dir
, fd
.cFileName
);
3362 } while (FindNextFileW(hff
, &fd
) != 0);
3366 /* Change to that directory */
3367 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
3369 status
= SetCurrentDirectoryW(string
);
3372 WCMD_print_error ();
3376 /* Save away the actual new directory, to store as current location */
3377 GetCurrentDirectoryW (sizeof(string
)/sizeof(WCHAR
), string
);
3379 /* Restore old directory if drive letter would change, and
3380 CD x:\directory /D (or pushd c:\directory) not supplied */
3381 if ((strstrW(quals
, parmD
) == NULL
) &&
3382 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
3383 SetCurrentDirectoryW(cwd
);
3387 /* Set special =C: type environment variable, for drive letter of
3388 change of directory, even if path was restored due to missing
3389 /D (allows changing drive letter when not resident on that
3391 if ((string
[1] == ':') && IsCharAlphaW(string
[0])) {
3393 strcpyW(env
, equalW
);
3394 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
3396 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
3397 SetEnvironmentVariableW(env
, string
);
3404 /****************************************************************************
3407 * Set/Show the system date
3408 * FIXME: Can't change date yet
3411 void WCMD_setshow_date (void) {
3413 WCHAR curdate
[64], buffer
[64];
3415 static const WCHAR parmT
[] = {'/','T','\0'};
3417 if (strlenW(param1
) == 0) {
3418 if (GetDateFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
3419 curdate
, sizeof(curdate
)/sizeof(WCHAR
))) {
3420 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
3421 if (strstrW (quals
, parmT
) == NULL
) {
3422 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
3423 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
3425 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
3429 else WCMD_print_error ();
3432 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
3436 /****************************************************************************
3438 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3441 static int WCMD_compare( const void *a
, const void *b
)
3444 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
3445 r
= CompareStringW( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
3446 *str_a
, strcspnW(*str_a
, equalW
), *str_b
, strcspnW(*str_b
, equalW
) );
3447 if( r
== CSTR_LESS_THAN
) return -1;
3448 if( r
== CSTR_GREATER_THAN
) return 1;
3452 /****************************************************************************
3453 * WCMD_setshow_sortenv
3455 * sort variables into order for display
3456 * Optionally only display those who start with a stub
3457 * returns the count displayed
3459 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
3461 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
3464 if (stub
) stublen
= strlenW(stub
);
3466 /* count the number of strings, and the total length */
3468 len
+= (strlenW(&s
[len
]) + 1);
3472 /* add the strings to an array */
3473 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
3477 for( i
=1; i
<count
; i
++ )
3478 str
[i
] = str
[i
-1] + strlenW(str
[i
-1]) + 1;
3480 /* sort the array */
3481 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
3484 for( i
=0; i
<count
; i
++ ) {
3485 if (!stub
|| CompareStringW(LOCALE_USER_DEFAULT
,
3486 NORM_IGNORECASE
| SORT_STRINGSORT
,
3487 str
[i
], stublen
, stub
, -1) == CSTR_EQUAL
) {
3488 /* Don't display special internal variables */
3489 if (str
[i
][0] != '=') {
3490 WCMD_output_asis(str
[i
]);
3491 WCMD_output_asis(newlineW
);
3498 return displayedcount
;
3501 /****************************************************************************
3502 * WCMD_getprecedence
3503 * Return the precedence of a particular operator
3505 static int WCMD_getprecedence(const WCHAR in
)
3546 /****************************************************************************
3548 * Push either a number or name (environment variable) onto the supplied
3551 static void WCMD_pushnumber(WCHAR
*var
, int num
, VARSTACK
**varstack
) {
3552 VARSTACK
*thisstack
= heap_alloc(sizeof(VARSTACK
));
3553 thisstack
->isnum
= (var
== NULL
);
3555 thisstack
->variable
= var
;
3556 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var
));
3558 thisstack
->value
= num
;
3559 WINE_TRACE("Pushed number %d\n", num
);
3561 thisstack
->next
= *varstack
;
3562 *varstack
= thisstack
;
3565 /****************************************************************************
3567 * Returns the value of the top number or environment variable on the stack
3568 * and leaves the item on the stack.
3570 static int WCMD_peeknumber(VARSTACK
**varstack
) {
3575 thisvar
= *varstack
;
3576 if (!thisvar
->isnum
) {
3577 WCHAR tmpstr
[MAXSTRING
];
3578 if (GetEnvironmentVariableW(thisvar
->variable
, tmpstr
, MAXSTRING
)) {
3579 result
= strtoulW(tmpstr
,NULL
,0);
3581 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar
->variable
), result
);
3583 result
= thisvar
->value
;
3586 WINE_TRACE("Peeked number %d\n", result
);
3590 /****************************************************************************
3592 * Returns the value of the top number or environment variable on the stack
3593 * and removes the item from the stack.
3595 static int WCMD_popnumber(VARSTACK
**varstack
) {
3600 thisvar
= *varstack
;
3601 result
= WCMD_peeknumber(varstack
);
3602 if (!thisvar
->isnum
) heap_free(thisvar
->variable
);
3603 *varstack
= thisvar
->next
;
3606 WINE_TRACE("Popped number %d\n", result
);
3610 /****************************************************************************
3612 * Push an operator onto the supplied stack
3614 static void WCMD_pushoperator(WCHAR op
, int precedence
, OPSTACK
**opstack
) {
3615 OPSTACK
*thisstack
= heap_alloc(sizeof(OPSTACK
));
3616 thisstack
->precedence
= precedence
;
3618 thisstack
->next
= *opstack
;
3619 WINE_TRACE("Pushed operator %c\n", op
);
3620 *opstack
= thisstack
;
3623 /****************************************************************************
3625 * Returns the operator from the top of the stack and removes the item from
3628 static WCHAR
WCMD_popoperator(OPSTACK
**opstack
) {
3634 result
= thisop
->op
;
3635 *opstack
= thisop
->next
;
3638 WINE_TRACE("Popped operator %c\n", result
);
3642 /****************************************************************************
3644 * Actions the top operator on the stack against the first and sometimes
3645 * second value on the variable stack, and pushes the result
3646 * Returns non-zero on error.
3648 static int WCMD_reduce(OPSTACK
**opstack
, VARSTACK
**varstack
) {
3653 if (!*opstack
|| !*varstack
) {
3654 WINE_TRACE("No operators for the reduce\n");
3655 return WCMD_NOOPERATOR
;
3658 /* Remove the top operator */
3660 *opstack
= (*opstack
)->next
;
3661 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop
->op
);
3663 /* One variable operators */
3664 var1
= WCMD_popnumber(varstack
);
3665 switch (thisop
->op
) {
3666 case '!': WCMD_pushnumber(NULL
, !var1
, varstack
);
3668 case '~': WCMD_pushnumber(NULL
, ~var1
, varstack
);
3670 case OP_POSITIVE
: WCMD_pushnumber(NULL
, var1
, varstack
);
3672 case OP_NEGATIVE
: WCMD_pushnumber(NULL
, -var1
, varstack
);
3676 /* Two variable operators */
3678 WINE_TRACE("No operands left for the reduce?\n");
3679 return WCMD_NOOPERAND
;
3681 switch (thisop
->op
) {
3686 break; /* Handled above */
3687 case '*': var2
= WCMD_popnumber(varstack
);
3688 WCMD_pushnumber(NULL
, var2
*var1
, varstack
);
3690 case '/': var2
= WCMD_popnumber(varstack
);
3691 if (var1
== 0) return WCMD_DIVIDEBYZERO
;
3692 WCMD_pushnumber(NULL
, var2
/var1
, varstack
);
3694 case '+': var2
= WCMD_popnumber(varstack
);
3695 WCMD_pushnumber(NULL
, var2
+var1
, varstack
);
3697 case '-': var2
= WCMD_popnumber(varstack
);
3698 WCMD_pushnumber(NULL
, var2
-var1
, varstack
);
3700 case '&': var2
= WCMD_popnumber(varstack
);
3701 WCMD_pushnumber(NULL
, var2
&var1
, varstack
);
3703 case '%': var2
= WCMD_popnumber(varstack
);
3704 if (var1
== 0) return WCMD_DIVIDEBYZERO
;
3705 WCMD_pushnumber(NULL
, var2
%var1
, varstack
);
3707 case '^': var2
= WCMD_popnumber(varstack
);
3708 WCMD_pushnumber(NULL
, var2
^var1
, varstack
);
3710 case '<': var2
= WCMD_popnumber(varstack
);
3711 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3712 which differs from the compiler (for example gcc) so being explicit. */
3713 if (var1
< 0 || var1
>= (8 * sizeof(INT
))) {
3714 WCMD_pushnumber(NULL
, 0, varstack
);
3716 WCMD_pushnumber(NULL
, var2
<<var1
, varstack
);
3719 case '>': var2
= WCMD_popnumber(varstack
);
3720 WCMD_pushnumber(NULL
, var2
>>var1
, varstack
);
3722 case '|': var2
= WCMD_popnumber(varstack
);
3723 WCMD_pushnumber(NULL
, var2
|var1
, varstack
);
3739 /* The left of an equals must be one variable */
3740 if (!(*varstack
) || (*varstack
)->isnum
) {
3741 return WCMD_NOOPERAND
;
3744 /* Make the number stack grow by inserting the value of the variable */
3745 var2
= WCMD_peeknumber(varstack
);
3746 WCMD_pushnumber(NULL
, var2
, varstack
);
3747 WCMD_pushnumber(NULL
, var1
, varstack
);
3749 /* Make the operand stack grow by pushing the assign operator plus the
3750 operator to perform */
3751 while (calcassignments
[i
].op
!= ' ' &&
3752 calcassignments
[i
].calculatedop
!= thisop
->op
) {
3755 if (calcassignments
[i
].calculatedop
== ' ') {
3756 WINE_ERR("Unexpected operator %c\n", thisop
->op
);
3757 return WCMD_NOOPERATOR
;
3759 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack
);
3760 WCMD_pushoperator(calcassignments
[i
].op
,
3761 WCMD_getprecedence(calcassignments
[i
].op
), opstack
);
3767 WCHAR intFormat
[] = {'%','d','\0'};
3768 WCHAR result
[MAXSTRING
];
3770 /* Build the result, then push it onto the stack */
3771 sprintfW(result
, intFormat
, var1
);
3772 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack
)->variable
),
3773 wine_dbgstr_w(result
));
3774 SetEnvironmentVariableW((*varstack
)->variable
, result
);
3775 var2
= WCMD_popnumber(varstack
);
3776 WCMD_pushnumber(NULL
, var1
, varstack
);
3780 default: WINE_ERR("Unrecognized operator %c\n", thisop
->op
);
3788 /****************************************************************************
3789 * WCMD_handleExpression
3790 * Handles an expression provided to set /a - If it finds brackets, it uses
3791 * recursion to process the parts in brackets.
3793 static int WCMD_handleExpression(WCHAR
**expr
, int *ret
, int depth
)
3795 static const WCHAR mathDelims
[] = {' ','\t','(',')','!','~','-','*','/','%',
3796 '+','<','>','&','^','|','=',',','\0' };
3799 BOOL lastwasnumber
= FALSE
; /* FALSE makes a minus at the start of the expression easier to handle */
3800 OPSTACK
*opstackhead
= NULL
;
3801 VARSTACK
*varstackhead
= NULL
;
3802 WCHAR foundhalf
= 0;
3805 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr
));
3808 /* Iterate through until whole expression is processed */
3809 while (pos
&& *pos
) {
3812 /* Skip whitespace to get to the next character to process*/
3813 while (*pos
&& (*pos
==' ' || *pos
=='\t')) pos
++;
3814 if (!*pos
) goto exprreturn
;
3816 /* If we have found anything other than an operator then it's a number/variable */
3817 if (strchrW(mathDelims
, *pos
) == NULL
) {
3818 WCHAR
*parmstart
, *parm
, *dupparm
;
3821 /* Cannot have an expression with var/number twice, without an operator
3822 in-between, nor or number following a half constructed << or >> operator */
3823 if (lastwasnumber
|| foundhalf
) {
3824 rc
= WCMD_NOOPERATOR
;
3825 goto exprerrorreturn
;
3827 lastwasnumber
= TRUE
;
3829 if (isdigitW(*pos
)) {
3830 /* For a number - just push it onto the stack */
3831 int num
= strtoulW(pos
, &nextpos
, 0);
3832 WCMD_pushnumber(NULL
, num
, &varstackhead
);
3835 /* Verify the number was validly formed */
3836 if (*nextpos
&& (strchrW(mathDelims
, *nextpos
) == NULL
)) {
3837 rc
= WCMD_BADHEXOCT
;
3838 goto exprerrorreturn
;
3842 /* For a variable - just push it onto the stack */
3843 parm
= WCMD_parameter_with_delims(pos
, 0, &parmstart
, FALSE
, FALSE
, mathDelims
);
3844 dupparm
= heap_strdupW(parm
);
3845 WCMD_pushnumber(dupparm
, 0, &varstackhead
);
3846 pos
= parmstart
+ strlenW(dupparm
);
3851 /* We have found an operator. Some operators are one character, some two, and the minus
3852 and plus signs need special processing as they can be either operators or just influence
3853 the parameter which follows them */
3854 if (foundhalf
&& (*pos
!= foundhalf
)) {
3855 /* Badly constructed operator pair */
3856 rc
= WCMD_NOOPERATOR
;
3857 goto exprerrorreturn
;
3860 treatasnumber
= FALSE
; /* We are processing an operand */
3863 /* > and < are special as they are double character operators (and spaces can be between them!)
3864 If we see these for the first time, set a flag, and second time around we continue.
3865 Note these double character operators are stored as just one of the characters on the stack */
3867 case '<': if (!foundhalf
) {
3872 /* We have found the rest, so clear up the knowledge of the half completed part and
3873 drop through to normal operator processing */
3877 case '=': if (*pos
=='=') {
3878 /* = is special cased as if the last was an operator then we may have e.g. += or
3879 *= etc which we need to handle by replacing the operator that is on the stack
3880 with a calculated assignment equivalent */
3881 if (!lastwasnumber
&& opstackhead
) {
3883 while (calcassignments
[i
].op
!= ' ' && calcassignments
[i
].op
!= opstackhead
->op
) {
3886 if (calcassignments
[i
].op
== ' ') {
3887 rc
= WCMD_NOOPERAND
;
3888 goto exprerrorreturn
;
3890 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3891 when the general operator handling happens further down. */
3892 *pos
= calcassignments
[i
].calculatedop
;
3893 WCMD_popoperator(&opstackhead
);
3899 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3900 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3901 case '+': if (!lastwasnumber
&& *pos
=='+') *pos
= OP_POSITIVE
;
3903 case '-': if (!lastwasnumber
&& *pos
=='-') *pos
= OP_NEGATIVE
;
3906 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3907 case '!': /* drop through */
3908 case '~': /* drop through */
3909 case '/': /* drop through */
3910 case '%': /* drop through */
3911 case '&': /* drop through */
3912 case '^': /* drop through */
3913 case '*': /* drop through */
3915 /* General code for handling most of the operators - look at the
3916 precedence of the top item on the stack, and see if we need to
3917 action the stack before we push something else onto it. */
3919 int precedence
= WCMD_getprecedence(*pos
);
3920 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos
,
3921 precedence
, !opstackhead
?-1:opstackhead
->precedence
);
3923 /* In general, for things with the same precedence, reduce immediately
3924 except for assignments and unary operators which do not */
3925 while (!rc
&& opstackhead
&&
3926 ((opstackhead
->precedence
> precedence
) ||
3927 ((opstackhead
->precedence
== precedence
) &&
3928 (precedence
!= 1) && (precedence
!= 8)))) {
3929 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
3931 if (rc
) goto exprerrorreturn
;
3932 WCMD_pushoperator(*pos
, precedence
, &opstackhead
);
3937 /* comma means start a new expression, ie calculate what we have */
3940 int prevresult
= -1;
3941 WINE_TRACE("Found expression delimiter - reducing exising stacks\n");
3942 while (!rc
&& opstackhead
) {
3943 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
3945 if (rc
) goto exprerrorreturn
;
3946 /* If we have anything other than one number left, error
3947 otherwise throw the number away */
3948 if (!varstackhead
|| varstackhead
->next
) {
3949 rc
= WCMD_NOOPERATOR
;
3950 goto exprerrorreturn
;
3952 prevresult
= WCMD_popnumber(&varstackhead
);
3953 WINE_TRACE("Expression resolved to %d\n", prevresult
);
3954 heap_free(varstackhead
);
3955 varstackhead
= NULL
;
3960 /* Open bracket - use iteration to parse the inner expression, then continue */
3964 rc
= WCMD_handleExpression(&pos
, &exprresult
, depth
+1);
3965 if (rc
) goto exprerrorreturn
;
3966 WCMD_pushnumber(NULL
, exprresult
, &varstackhead
);
3970 /* Close bracket - we have finished this depth, calculate and return */
3973 treatasnumber
= TRUE
; /* Things in brackets result in a number */
3976 goto exprerrorreturn
;
3982 WINE_ERR("Unrecognized operator %c\n", *pos
);
3985 lastwasnumber
= treatasnumber
;
3991 /* We need to reduce until we have a single number (or variable) on the
3992 stack and set the return value to that */
3993 while (!rc
&& opstackhead
) {
3994 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
3996 if (rc
) goto exprerrorreturn
;
3998 /* If we have anything other than one number left, error
3999 otherwise throw the number away */
4000 if (!varstackhead
|| varstackhead
->next
) {
4001 rc
= WCMD_NOOPERATOR
;
4002 goto exprerrorreturn
;
4005 /* Now get the number (and convert if it's just a variable name) */
4006 *ret
= WCMD_popnumber(&varstackhead
);
4009 /* Free all remaining memory */
4010 while (opstackhead
) WCMD_popoperator(&opstackhead
);
4011 while (varstackhead
) WCMD_popnumber(&varstackhead
);
4013 WINE_TRACE("Returning result %d, rc %d\n", *ret
, rc
);
4017 /****************************************************************************
4020 * Set/Show the environment variables
4023 void WCMD_setshow_env (WCHAR
*s
) {
4028 static const WCHAR parmP
[] = {'/','P','\0'};
4029 static const WCHAR parmA
[] = {'/','A','\0'};
4030 WCHAR string
[MAXSTRING
];
4032 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
4033 env
= GetEnvironmentStringsW();
4034 WCMD_setshow_sortenv( env
, NULL
);
4038 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4039 if (CompareStringW(LOCALE_USER_DEFAULT
,
4040 NORM_IGNORECASE
| SORT_STRINGSORT
,
4041 s
, 2, parmP
, -1) == CSTR_EQUAL
) {
4045 while (*s
&& (*s
==' ' || *s
=='\t')) s
++;
4046 /* set /P "var=value"jim ignores anything after the last quote */
4049 lastquote
= WCMD_strip_quotes(s
);
4050 if (lastquote
) *lastquote
= 0x00;
4051 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s
));
4054 /* If no parameter, or no '=' sign, return an error */
4055 if (!(*s
) || ((p
= strchrW (s
, '=')) == NULL
)) {
4056 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
4060 /* Output the prompt */
4062 if (strlenW(p
) != 0) WCMD_output_asis(p
);
4064 /* Read the reply */
4065 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
)/sizeof(WCHAR
), &count
);
4067 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
4068 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
4069 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
4070 wine_dbgstr_w(string
));
4071 status
= SetEnvironmentVariableW(s
, string
);
4074 /* See if /A supplied, and if so calculate the results of all the expressions */
4075 } else if (CompareStringW(LOCALE_USER_DEFAULT
,
4076 NORM_IGNORECASE
| SORT_STRINGSORT
,
4077 s
, 2, parmA
, -1) == CSTR_EQUAL
) {
4078 /* /A supplied, so evaluate expressions and set variables appropriately */
4079 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4080 /* of the final computation */
4086 /* Remove all quotes before doing any calculations */
4087 thisexpr
= heap_alloc((strlenW(s
+2)+1) * sizeof(WCHAR
));
4091 if (*src
!= '"') *dst
++ = *src
;
4096 /* Now calculate the results of the expression */
4098 rc
= WCMD_handleExpression(&src
, &result
, 0);
4099 heap_free(thisexpr
);
4101 /* If parsing failed, issue the error message */
4103 WCMD_output_stderr(WCMD_LoadMessage(rc
));
4107 /* If we have no context (interactive or cmd.exe /c) print the final result */
4109 static const WCHAR fmt
[] = {'%','d','\0'};
4110 sprintfW(string
, fmt
, result
);
4111 WCMD_output_asis(string
);
4117 /* set "var=value"jim ignores anything after the last quote */
4120 lastquote
= WCMD_strip_quotes(s
);
4121 if (lastquote
) *lastquote
= 0x00;
4122 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s
));
4125 p
= strchrW (s
, '=');
4127 env
= GetEnvironmentStringsW();
4128 if (WCMD_setshow_sortenv( env
, s
) == 0) {
4129 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
4136 if (strlenW(p
) == 0) p
= NULL
;
4137 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
4139 status
= SetEnvironmentVariableW(s
, p
);
4140 gle
= GetLastError();
4141 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
4143 } else if (!status
) WCMD_print_error();
4144 else errorlevel
= 0;
4148 /****************************************************************************
4151 * Set/Show the path environment variable
4154 void WCMD_setshow_path (const WCHAR
*args
) {
4158 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
4159 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
4161 if (strlenW(param1
) == 0 && strlenW(param2
) == 0) {
4162 status
= GetEnvironmentVariableW(pathW
, string
, sizeof(string
)/sizeof(WCHAR
));
4164 WCMD_output_asis ( pathEqW
);
4165 WCMD_output_asis ( string
);
4166 WCMD_output_asis ( newlineW
);
4169 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH
));
4173 if (*args
== '=') args
++; /* Skip leading '=' */
4174 status
= SetEnvironmentVariableW(pathW
, args
);
4175 if (!status
) WCMD_print_error();
4179 /****************************************************************************
4180 * WCMD_setshow_prompt
4182 * Set or show the command prompt.
4185 void WCMD_setshow_prompt (void) {
4188 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
4190 if (strlenW(param1
) == 0) {
4191 SetEnvironmentVariableW(promptW
, NULL
);
4195 while ((*s
== '=') || (*s
== ' ') || (*s
== '\t')) s
++;
4196 if (strlenW(s
) == 0) {
4197 SetEnvironmentVariableW(promptW
, NULL
);
4199 else SetEnvironmentVariableW(promptW
, s
);
4203 /****************************************************************************
4206 * Set/Show the system time
4207 * FIXME: Can't change time yet
4210 void WCMD_setshow_time (void) {
4212 WCHAR curtime
[64], buffer
[64];
4215 static const WCHAR parmT
[] = {'/','T','\0'};
4217 if (strlenW(param1
) == 0) {
4219 if (GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
4220 curtime
, sizeof(curtime
)/sizeof(WCHAR
))) {
4221 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME
), curtime
);
4222 if (strstrW (quals
, parmT
) == NULL
) {
4223 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
4224 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
4226 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
4230 else WCMD_print_error ();
4233 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
4237 /****************************************************************************
4240 * Shift batch parameters.
4241 * Optional /n says where to start shifting (n=0-8)
4244 void WCMD_shift (const WCHAR
*args
) {
4247 if (context
!= NULL
) {
4248 WCHAR
*pos
= strchrW(args
, '/');
4253 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
4254 start
= (*(pos
+1) - '0');
4256 SetLastError(ERROR_INVALID_PARAMETER
);
4261 WINE_TRACE("Shifting variables, starting at %d\n", start
);
4262 for (i
=start
;i
<=8;i
++) {
4263 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
4265 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
4270 /****************************************************************************
4273 void WCMD_start(const WCHAR
*args
)
4275 static const WCHAR exeW
[] = {'\\','c','o','m','m','a','n','d',
4276 '\\','s','t','a','r','t','.','e','x','e',0};
4277 WCHAR file
[MAX_PATH
];
4280 PROCESS_INFORMATION pi
;
4282 GetWindowsDirectoryW( file
, MAX_PATH
);
4283 strcatW( file
, exeW
);
4284 cmdline
= heap_alloc( (strlenW(file
) + strlenW(args
) + 2) * sizeof(WCHAR
) );
4285 strcpyW( cmdline
, file
);
4286 strcatW( cmdline
, spaceW
);
4287 strcatW( cmdline
, args
);
4289 memset( &st
, 0, sizeof(STARTUPINFOW
) );
4290 st
.cb
= sizeof(STARTUPINFOW
);
4292 if (CreateProcessW( file
, cmdline
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pi
))
4294 WaitForSingleObject( pi
.hProcess
, INFINITE
);
4295 GetExitCodeProcess( pi
.hProcess
, &errorlevel
);
4296 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
4297 CloseHandle(pi
.hProcess
);
4298 CloseHandle(pi
.hThread
);
4302 SetLastError(ERROR_FILE_NOT_FOUND
);
4303 WCMD_print_error ();
4309 /****************************************************************************
4312 * Set the console title
4314 void WCMD_title (const WCHAR
*args
) {
4315 SetConsoleTitleW(args
);
4318 /****************************************************************************
4321 * Copy a file to standard output.
4324 void WCMD_type (WCHAR
*args
) {
4328 BOOL writeHeaders
= FALSE
;
4330 if (param1
[0] == 0x00) {
4331 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
4335 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
4337 /* Loop through all args */
4340 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
4348 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
4349 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4350 FILE_ATTRIBUTE_NORMAL
, NULL
);
4351 if (h
== INVALID_HANDLE_VALUE
) {
4352 WCMD_print_error ();
4353 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
4357 static const WCHAR fmt
[] = {'\n','%','1','\n','\n','\0'};
4358 WCMD_output(fmt
, thisArg
);
4360 while (WCMD_ReadFile(h
, buffer
, sizeof(buffer
)/sizeof(WCHAR
) - 1, &count
)) {
4361 if (count
== 0) break; /* ReadFile reports success on EOF! */
4363 WCMD_output_asis (buffer
);
4370 /****************************************************************************
4373 * Output either a file or stdin to screen in pages
4376 void WCMD_more (WCHAR
*args
) {
4381 WCHAR moreStrPage
[100];
4384 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
4385 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
4386 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
4387 ')',' ','-','-','\n','\0'};
4388 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
4390 /* Prefix the NLS more with '-- ', then load the text */
4392 strcpyW(moreStr
, moreStart
);
4393 LoadStringW(hinst
, WCMD_MORESTR
, &moreStr
[3],
4394 (sizeof(moreStr
)/sizeof(WCHAR
))-3);
4396 if (param1
[0] == 0x00) {
4398 /* Wine implements pipes via temporary files, and hence stdin is
4399 effectively reading from the file. This means the prompts for
4400 more are satisfied by the next line from the input (file). To
4401 avoid this, ensure stdin is to the console */
4402 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
4403 HANDLE hConIn
= CreateFileW(conInW
, GENERIC_READ
| GENERIC_WRITE
,
4404 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4405 FILE_ATTRIBUTE_NORMAL
, 0);
4406 WINE_TRACE("No parms - working probably in pipe mode\n");
4407 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
4409 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4410 once you get in this bit unless due to a pipe, it's going to end badly... */
4411 wsprintfW(moreStrPage
, moreFmt
, moreStr
);
4413 WCMD_enter_paged_mode(moreStrPage
);
4414 while (WCMD_ReadFile(hstdin
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
)) {
4415 if (count
== 0) break; /* ReadFile reports success on EOF! */
4417 WCMD_output_asis (buffer
);
4419 WCMD_leave_paged_mode();
4421 /* Restore stdin to what it was */
4422 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
4423 CloseHandle(hConIn
);
4427 BOOL needsPause
= FALSE
;
4429 /* Loop through all args */
4430 WINE_TRACE("Parms supplied - working through each file\n");
4431 WCMD_enter_paged_mode(moreStrPage
);
4434 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
4442 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, 100);
4443 WCMD_leave_paged_mode();
4444 WCMD_output_asis(moreStrPage
);
4445 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
4446 WCMD_enter_paged_mode(moreStrPage
);
4450 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
4451 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4452 FILE_ATTRIBUTE_NORMAL
, NULL
);
4453 if (h
== INVALID_HANDLE_VALUE
) {
4454 WCMD_print_error ();
4455 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
4459 ULONG64 fileLen
= 0;
4460 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
4462 /* Get the file size */
4463 GetFileAttributesExW(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
4464 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
4467 while (WCMD_ReadFile(h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
)) {
4468 if (count
== 0) break; /* ReadFile reports success on EOF! */
4472 /* Update % count (would be used in WCMD_output_asis as prompt) */
4473 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
4475 WCMD_output_asis (buffer
);
4481 WCMD_leave_paged_mode();
4485 /****************************************************************************
4488 * Display verify flag.
4489 * FIXME: We don't actually do anything with the verify flag other than toggle
4493 void WCMD_verify (const WCHAR
*args
) {
4497 count
= strlenW(args
);
4499 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
4500 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
4503 if (lstrcmpiW(args
, onW
) == 0) {
4507 else if (lstrcmpiW(args
, offW
) == 0) {
4508 verify_mode
= FALSE
;
4511 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR
));
4514 /****************************************************************************
4517 * Display version info.
4520 void WCMD_version (void) {
4522 WCMD_output_asis (version_string
);
4526 /****************************************************************************
4529 * Display volume information (set_label = FALSE)
4530 * Additionally set volume label (set_label = TRUE)
4531 * Returns 1 on success, 0 otherwise
4534 int WCMD_volume(BOOL set_label
, const WCHAR
*path
)
4536 DWORD count
, serial
;
4537 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
4540 if (strlenW(path
) == 0) {
4541 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
4543 WCMD_print_error ();
4546 status
= GetVolumeInformationW(NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
4547 &serial
, NULL
, NULL
, NULL
, 0);
4550 static const WCHAR fmt
[] = {'%','s','\\','\0'};
4551 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
4552 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
4555 wsprintfW (curdir
, fmt
, path
);
4556 status
= GetVolumeInformationW(curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
4561 WCMD_print_error ();
4564 if (label
[0] != '\0') {
4565 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL
),
4569 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL
),
4572 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO
),
4573 HIWORD(serial
), LOWORD(serial
));
4575 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
4576 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
)/sizeof(WCHAR
), &count
);
4578 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
4579 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
4581 if (strlenW(path
) != 0) {
4582 if (!SetVolumeLabelW(curdir
, string
)) WCMD_print_error ();
4585 if (!SetVolumeLabelW(NULL
, string
)) WCMD_print_error ();
4591 /**************************************************************************
4594 * Exit either the process, or just this batch program
4598 void WCMD_exit (CMD_LIST
**cmdList
) {
4600 static const WCHAR parmB
[] = {'/','B','\0'};
4601 int rc
= atoiW(param1
); /* Note: atoi of empty parameter is 0 */
4603 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
4605 context
-> skip_rest
= TRUE
;
4613 /*****************************************************************************
4616 * Lists or sets file associations (assoc = TRUE)
4617 * Lists or sets file types (assoc = FALSE)
4619 void WCMD_assoc (const WCHAR
*args
, BOOL assoc
) {
4622 DWORD accessOptions
= KEY_READ
;
4624 LONG rc
= ERROR_SUCCESS
;
4625 WCHAR keyValue
[MAXSTRING
];
4626 DWORD valueLen
= MAXSTRING
;
4628 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
4629 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4631 /* See if parameter includes '=' */
4633 newValue
= strchrW(args
, '=');
4634 if (newValue
) accessOptions
|= KEY_WRITE
;
4636 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4637 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, nullW
, 0,
4638 accessOptions
, &key
) != ERROR_SUCCESS
) {
4639 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4643 /* If no parameters then list all associations */
4644 if (*args
== 0x00) {
4647 /* Enumerate all the keys */
4648 while (rc
!= ERROR_NO_MORE_ITEMS
) {
4649 WCHAR keyName
[MAXSTRING
];
4652 /* Find the next value */
4653 nameLen
= MAXSTRING
;
4654 rc
= RegEnumKeyExW(key
, index
++, keyName
, &nameLen
, NULL
, NULL
, NULL
, NULL
);
4656 if (rc
== ERROR_SUCCESS
) {
4658 /* Only interested in extension ones if assoc, or others
4660 if ((keyName
[0] == '.' && assoc
) ||
4661 (!(keyName
[0] == '.') && (!assoc
)))
4663 WCHAR subkey
[MAXSTRING
];
4664 strcpyW(subkey
, keyName
);
4665 if (!assoc
) strcatW(subkey
, shOpCmdW
);
4667 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
4669 valueLen
= sizeof(keyValue
)/sizeof(WCHAR
);
4670 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
4671 WCMD_output_asis(keyName
);
4672 WCMD_output_asis(equalW
);
4673 /* If no default value found, leave line empty after '=' */
4674 if (rc
== ERROR_SUCCESS
) {
4675 WCMD_output_asis(keyValue
);
4677 WCMD_output_asis(newlineW
);
4678 RegCloseKey(readKey
);
4686 /* Parameter supplied - if no '=' on command line, it's a query */
4687 if (newValue
== NULL
) {
4689 WCHAR subkey
[MAXSTRING
];
4691 /* Query terminates the parameter at the first space */
4692 strcpyW(keyValue
, args
);
4693 space
= strchrW(keyValue
, ' ');
4694 if (space
) *space
=0x00;
4696 /* Set up key name */
4697 strcpyW(subkey
, keyValue
);
4698 if (!assoc
) strcatW(subkey
, shOpCmdW
);
4700 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
4702 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
4703 WCMD_output_asis(args
);
4704 WCMD_output_asis(equalW
);
4705 /* If no default value found, leave line empty after '=' */
4706 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
4707 WCMD_output_asis(newlineW
);
4708 RegCloseKey(readKey
);
4711 WCHAR msgbuffer
[MAXSTRING
];
4713 /* Load the translated 'File association not found' */
4715 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
4717 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
4719 WCMD_output_stderr(msgbuffer
, keyValue
);
4723 /* Not a query - it's a set or clear of a value */
4726 WCHAR subkey
[MAXSTRING
];
4728 /* Get pointer to new value */
4732 /* Set up key name */
4733 strcpyW(subkey
, args
);
4734 if (!assoc
) strcatW(subkey
, shOpCmdW
);
4736 /* If nothing after '=' then clear value - only valid for ASSOC */
4737 if (*newValue
== 0x00) {
4739 if (assoc
) rc
= RegDeleteKeyW(key
, args
);
4740 if (assoc
&& rc
== ERROR_SUCCESS
) {
4741 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args
));
4743 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
4748 WCHAR msgbuffer
[MAXSTRING
];
4750 /* Load the translated 'File association not found' */
4752 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
,
4753 sizeof(msgbuffer
)/sizeof(WCHAR
));
4755 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
,
4756 sizeof(msgbuffer
)/sizeof(WCHAR
));
4758 WCMD_output_stderr(msgbuffer
, keyValue
);
4762 /* It really is a set value = contents */
4764 rc
= RegCreateKeyExW(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
4765 accessOptions
, NULL
, &readKey
, NULL
);
4766 if (rc
== ERROR_SUCCESS
) {
4767 rc
= RegSetValueExW(readKey
, NULL
, 0, REG_SZ
,
4769 sizeof(WCHAR
) * (strlenW(newValue
) + 1));
4770 RegCloseKey(readKey
);
4773 if (rc
!= ERROR_SUCCESS
) {
4777 WCMD_output_asis(args
);
4778 WCMD_output_asis(equalW
);
4779 WCMD_output_asis(newValue
);
4780 WCMD_output_asis(newlineW
);
4790 /****************************************************************************
4793 * Colors the terminal screen.
4796 void WCMD_color (void) {
4798 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
4799 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
4801 if (param1
[0] != 0x00 && strlenW(param1
) > 2) {
4802 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR
));
4806 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
4812 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
4817 /* Convert the color hex digits */
4818 if (param1
[0] == 0x00) {
4819 color
= defaultColor
;
4821 color
= strtoulW(param1
, NULL
, 16);
4824 /* Fail if fg == bg color */
4825 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
4830 /* Set the current screen contents and ensure all future writes
4831 remain this color */
4832 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
4833 SetConsoleTextAttribute(hStdOut
, color
);