2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
37 extern int defaultColor
;
38 extern BOOL echo_mode
;
39 extern BOOL interactive
;
41 struct env_stack
*pushd_directories
;
42 const WCHAR dotW
[] = {'.','\0'};
43 const WCHAR dotdotW
[] = {'.','.','\0'};
44 const WCHAR nullW
[] = {'\0'};
45 const WCHAR starW
[] = {'*','\0'};
46 const WCHAR slashW
[] = {'\\','\0'};
47 const WCHAR equalW
[] = {'=','\0'};
48 const WCHAR wildcardsW
[] = {'*','?','\0'};
49 const WCHAR slashstarW
[] = {'\\','*','\0'};
50 const WCHAR deviceW
[] = {'\\','\\','.','\\','\0'};
51 const WCHAR inbuilt
[][10] = {
52 {'C','A','L','L','\0'},
54 {'C','H','D','I','R','\0'},
56 {'C','O','P','Y','\0'},
57 {'C','T','T','Y','\0'},
58 {'D','A','T','E','\0'},
61 {'E','C','H','O','\0'},
62 {'E','R','A','S','E','\0'},
64 {'G','O','T','O','\0'},
65 {'H','E','L','P','\0'},
67 {'L','A','B','E','L','\0'},
69 {'M','K','D','I','R','\0'},
70 {'M','O','V','E','\0'},
71 {'P','A','T','H','\0'},
72 {'P','A','U','S','E','\0'},
73 {'P','R','O','M','P','T','\0'},
76 {'R','E','N','A','M','E','\0'},
78 {'R','M','D','I','R','\0'},
80 {'S','H','I','F','T','\0'},
81 {'S','T','A','R','T','\0'},
82 {'T','I','M','E','\0'},
83 {'T','I','T','L','E','\0'},
84 {'T','Y','P','E','\0'},
85 {'V','E','R','I','F','Y','\0'},
88 {'E','N','D','L','O','C','A','L','\0'},
89 {'S','E','T','L','O','C','A','L','\0'},
90 {'P','U','S','H','D','\0'},
91 {'P','O','P','D','\0'},
92 {'A','S','S','O','C','\0'},
93 {'C','O','L','O','R','\0'},
94 {'F','T','Y','P','E','\0'},
95 {'M','O','R','E','\0'},
96 {'C','H','O','I','C','E','\0'},
97 {'E','X','I','T','\0'}
99 static const WCHAR externals
[][10] = {
100 {'A','T','T','R','I','B','\0'},
101 {'X','C','O','P','Y','\0'}
103 static const WCHAR fslashW
[] = {'/','\0'};
104 static const WCHAR onW
[] = {'O','N','\0'};
105 static const WCHAR offW
[] = {'O','F','F','\0'};
106 static const WCHAR parmY
[] = {'/','Y','\0'};
107 static const WCHAR parmNoY
[] = {'/','-','Y','\0'};
108 static const WCHAR eqeqW
[] = {'=','=','\0'};
110 static HINSTANCE hinst
;
111 struct env_stack
*saved_environment
;
112 static BOOL verify_mode
= FALSE
;
114 /* set /a routines work from single character operators, but some of the
115 operators are multiple character ones, especially the assignment ones.
116 Temporarily represent these using the values below on the operator stack */
117 #define OP_POSITIVE 'P'
118 #define OP_NEGATIVE 'N'
119 #define OP_ASSSIGNMUL 'a'
120 #define OP_ASSSIGNDIV 'b'
121 #define OP_ASSSIGNMOD 'c'
122 #define OP_ASSSIGNADD 'd'
123 #define OP_ASSSIGNSUB 'e'
124 #define OP_ASSSIGNAND 'f'
125 #define OP_ASSSIGNNOT 'g'
126 #define OP_ASSSIGNOR 'h'
127 #define OP_ASSSIGNSHL 'i'
128 #define OP_ASSSIGNSHR 'j'
130 /* This maintains a stack of operators, holding both the operator precedence
131 and the single character representation of the operator in question */
132 typedef struct _OPSTACK
136 struct _OPSTACK
*next
;
139 /* This maintains a stack of values, where each value can either be a
140 numeric value, or a string represeting an environment variable */
141 typedef struct _VARSTACK
146 struct _VARSTACK
*next
;
149 /* This maintains a mapping between the calculated operator and the
150 single character representation for the assignment operators. */
155 } calcassignments
[] =
157 {'*', OP_ASSSIGNMUL
},
158 {'/', OP_ASSSIGNDIV
},
159 {'%', OP_ASSSIGNMOD
},
160 {'+', OP_ASSSIGNADD
},
161 {'-', OP_ASSSIGNSUB
},
162 {'&', OP_ASSSIGNAND
},
163 {'^', OP_ASSSIGNNOT
},
165 {'<', OP_ASSSIGNSHL
},
166 {'>', OP_ASSSIGNSHR
},
170 /**************************************************************************
173 * Issue a message and ask for confirmation, waiting on a valid answer.
175 * Returns True if Y (or A) answer is selected
176 * If optionAll contains a pointer, ALL is allowed, and if answered
180 static BOOL
WCMD_ask_confirm (const WCHAR
*message
, BOOL showSureText
,
184 WCHAR confirm
[MAXSTRING
];
185 WCHAR options
[MAXSTRING
];
186 WCHAR Ybuffer
[MAXSTRING
];
187 WCHAR Nbuffer
[MAXSTRING
];
188 WCHAR Abuffer
[MAXSTRING
];
189 WCHAR answer
[MAX_PATH
] = {'\0'};
192 /* Load the translated valid answers */
194 LoadStringW(hinst
, WCMD_CONFIRM
, confirm
, sizeof(confirm
)/sizeof(WCHAR
));
195 msgid
= optionAll
? WCMD_YESNOALL
: WCMD_YESNO
;
196 LoadStringW(hinst
, msgid
, options
, sizeof(options
)/sizeof(WCHAR
));
197 LoadStringW(hinst
, WCMD_YES
, Ybuffer
, sizeof(Ybuffer
)/sizeof(WCHAR
));
198 LoadStringW(hinst
, WCMD_NO
, Nbuffer
, sizeof(Nbuffer
)/sizeof(WCHAR
));
199 LoadStringW(hinst
, WCMD_ALL
, Abuffer
, sizeof(Abuffer
)/sizeof(WCHAR
));
201 /* Loop waiting on a valid answer */
206 WCMD_output_asis (message
);
208 WCMD_output_asis (confirm
);
209 WCMD_output_asis (options
);
210 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, sizeof(answer
)/sizeof(WCHAR
), &count
);
211 answer
[0] = toupperW(answer
[0]);
212 if (answer
[0] == Ybuffer
[0])
214 if (answer
[0] == Nbuffer
[0])
216 if (optionAll
&& answer
[0] == Abuffer
[0])
224 /****************************************************************************
227 * Clear the terminal screen.
230 void WCMD_clear_screen (void) {
232 /* Emulate by filling the screen from the top left to bottom right with
233 spaces, then moving the cursor to the top left afterwards */
234 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
235 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
237 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
242 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
246 FillConsoleOutputCharacterW(hStdOut
, ' ', screenSize
, topLeft
, &screenSize
);
247 SetConsoleCursorPosition(hStdOut
, topLeft
);
251 /****************************************************************************
254 * Change the default i/o device (ie redirect STDin/STDout).
257 void WCMD_change_tty (void) {
259 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
263 /****************************************************************************
268 void WCMD_choice (const WCHAR
* args
) {
270 static const WCHAR bellW
[] = {7,0};
271 static const WCHAR commaW
[] = {',',0};
272 static const WCHAR bracket_open
[] = {'[',0};
273 static const WCHAR bracket_close
[] = {']','?',0};
278 WCHAR
*my_command
= NULL
;
279 WCHAR opt_default
= 0;
280 DWORD opt_timeout
= 0;
287 have_console
= GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), &oldmode
);
290 my_command
= heap_strdupW(WCMD_skip_leading_spaces((WCHAR
*) args
));
292 ptr
= WCMD_skip_leading_spaces(my_command
);
293 while (*ptr
== '/') {
294 switch (toupperW(ptr
[1])) {
297 /* the colon is optional */
301 if (!*ptr
|| isspaceW(*ptr
)) {
302 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr
));
303 heap_free(my_command
);
307 /* remember the allowed keys (overwrite previous /C option) */
309 while (*ptr
&& (!isspaceW(*ptr
)))
313 /* terminate allowed chars */
315 ptr
= WCMD_skip_leading_spaces(&ptr
[1]);
317 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c
));
322 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
327 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
332 /* the colon is optional */
336 opt_default
= *ptr
++;
338 if (!opt_default
|| (*ptr
!= ',')) {
339 WINE_FIXME("bad option %s for /T\n", opt_default
? wine_dbgstr_w(ptr
) : "");
340 heap_free(my_command
);
346 while (((answer
[count
] = *ptr
)) && isdigitW(*ptr
) && (count
< 15)) {
352 opt_timeout
= atoiW(answer
);
354 ptr
= WCMD_skip_leading_spaces(ptr
);
358 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr
));
359 heap_free(my_command
);
365 WINE_FIXME("timeout not supported: %c,%d\n", opt_default
, opt_timeout
);
368 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), 0);
370 /* use default keys, when needed: localized versions of "Y"es and "No" */
372 LoadStringW(hinst
, WCMD_YES
, buffer
, sizeof(buffer
)/sizeof(WCHAR
));
373 LoadStringW(hinst
, WCMD_NO
, buffer
+ 1, sizeof(buffer
)/sizeof(WCHAR
) - 1);
378 /* print the question, when needed */
380 WCMD_output_asis(ptr
);
384 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c
));
388 /* print a list of all allowed answers inside brackets */
389 WCMD_output_asis(bracket_open
);
392 while ((answer
[0] = *ptr
++)) {
393 WCMD_output_asis(answer
);
395 WCMD_output_asis(commaW
);
397 WCMD_output_asis(bracket_close
);
402 /* FIXME: Add support for option /T */
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
)
441 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename
));
442 h
= CreateFileW(filename
, GENERIC_WRITE
, 0, NULL
,
443 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
446 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename
), GetLastError());
449 SetFilePointer (h
, 0, NULL
, FILE_END
);
450 if (!WriteFile(h
, &eof
, 1, NULL
, NULL
)) {
451 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename
), GetLastError());
460 /****************************************************************************
464 * optionally reading only until EOF (ascii copy)
465 * optionally appending onto an existing file (append)
466 * Returns TRUE on success
468 static BOOL
WCMD_ManualCopy(WCHAR
*srcname
, WCHAR
*dstname
, BOOL ascii
, BOOL append
)
472 DWORD bytesread
, byteswritten
;
474 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
475 wine_dbgstr_w(srcname
), wine_dbgstr_w(dstname
), append
);
477 in
= CreateFileW(srcname
, GENERIC_READ
, 0, NULL
,
478 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
480 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname
), GetLastError());
484 /* Open the output file, overwriting if not appending */
485 out
= CreateFileW(dstname
, GENERIC_WRITE
, 0, NULL
,
486 append
?OPEN_EXISTING
:CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
488 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname
), GetLastError());
493 /* Move to end of destination if we are going to append to it */
495 SetFilePointer(out
, 0, NULL
, FILE_END
);
498 /* Loop copying data from source to destination until EOF read */
501 char buffer
[MAXSTRING
];
503 ok
= ReadFile(in
, buffer
, MAXSTRING
, &bytesread
, NULL
);
506 /* Stop at first EOF */
508 char *ptr
= (char *)memchr((void *)buffer
, '\x1a', bytesread
);
509 if (ptr
) bytesread
= (ptr
- buffer
);
513 ok
= WriteFile(out
, buffer
, bytesread
, &byteswritten
, NULL
);
514 if (!ok
|| byteswritten
!= bytesread
) {
515 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
516 wine_dbgstr_w(dstname
), GetLastError());
520 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
521 wine_dbgstr_w(srcname
), GetLastError());
523 } while (ok
&& bytesread
> 0);
530 /****************************************************************************
533 * Copy a file or wildcarded set.
534 * For ascii/binary type copies, it gets complex:
535 * Syntax on command line is
536 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
537 * Where first /a or /b sets 'mode in operation' until another is found
538 * once another is found, it applies to the file preceding the /a or /b
539 * In addition each filename can contain wildcards
540 * To make matters worse, the + may be in the same parameter (i.e. no
541 * whitespace) or with whitespace separating it
543 * ASCII mode on read == read and stop at first EOF
544 * ASCII mode on write == append EOF to destination
545 * Binary == copy as-is
547 * Design of this is to build up a list of files which will be copied into a
548 * list, then work through the list file by file.
549 * If no destination is specified, it defaults to the name of the first file in
550 * the list, but the current directory.
554 void WCMD_copy(WCHAR
* args
) {
556 BOOL opt_d
, opt_v
, opt_n
, opt_z
, opt_y
, opt_noty
;
561 HANDLE hff
= INVALID_HANDLE_VALUE
;
562 int binarymode
= -1; /* -1 means use the default, 1 is binary, 0 ascii */
563 BOOL concatnextfilename
= FALSE
; /* True if we have just processed a + */
564 BOOL anyconcats
= FALSE
; /* Have we found any + options */
565 BOOL appendfirstsource
= FALSE
; /* Use first found filename as destination */
566 BOOL writtenoneconcat
= FALSE
; /* Remember when the first concatenated file done */
567 BOOL prompt
; /* Prompt before overwriting */
568 WCHAR destname
[MAX_PATH
]; /* Used in calculating the destination name */
569 BOOL destisdirectory
= FALSE
; /* Is the destination a directory? */
573 BOOL dstisdevice
= FALSE
;
574 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
576 typedef struct _COPY_FILES
578 struct _COPY_FILES
*next
;
583 COPY_FILES
*sourcelist
= NULL
;
584 COPY_FILES
*lastcopyentry
= NULL
;
585 COPY_FILES
*destination
= NULL
;
586 COPY_FILES
*thiscopy
= NULL
;
587 COPY_FILES
*prevcopy
= NULL
;
589 /* Assume we were successful! */
592 /* If no args supplied at all, report an error */
593 if (param1
[0] == 0x00) {
594 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG
));
599 opt_d
= opt_v
= opt_n
= opt_z
= opt_y
= opt_noty
= FALSE
;
601 /* Walk through all args, building up a list of files to process */
602 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
603 while (*(thisparam
)) {
607 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam
));
609 /* Handle switches */
610 if (*thisparam
== '/') {
611 while (*thisparam
== '/') {
613 if (toupperW(*thisparam
) == 'D') {
615 if (opt_d
) WINE_FIXME("copy /D support not implemented yet\n");
616 } else if (toupperW(*thisparam
) == 'Y') {
618 } else if (toupperW(*thisparam
) == '-' && toupperW(*(thisparam
+1)) == 'Y') {
620 } else if (toupperW(*thisparam
) == 'V') {
622 if (opt_v
) WINE_FIXME("copy /V support not implemented yet\n");
623 } else if (toupperW(*thisparam
) == 'N') {
625 if (opt_n
) WINE_FIXME("copy /N support not implemented yet\n");
626 } else if (toupperW(*thisparam
) == 'Z') {
628 if (opt_z
) WINE_FIXME("copy /Z support not implemented yet\n");
629 } else if (toupperW(*thisparam
) == 'A') {
630 if (binarymode
!= 0) {
632 WINE_TRACE("Subsequent files will be handled as ASCII\n");
633 if (destination
!= NULL
) {
634 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination
->name
));
635 destination
->binarycopy
= binarymode
;
636 } else if (lastcopyentry
!= NULL
) {
637 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry
->name
));
638 lastcopyentry
->binarycopy
= binarymode
;
641 } else if (toupperW(*thisparam
) == 'B') {
642 if (binarymode
!= 1) {
644 WINE_TRACE("Subsequent files will be handled as binary\n");
645 if (destination
!= NULL
) {
646 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination
->name
));
647 destination
->binarycopy
= binarymode
;
648 } else if (lastcopyentry
!= NULL
) {
649 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry
->name
));
650 lastcopyentry
->binarycopy
= binarymode
;
654 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam
));
659 /* This parameter was purely switches, get the next one */
660 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
664 /* We have found something which is not a switch. If could be anything of the form
665 sourcefilename (which could be destination too)
666 + (when filename + filename syntex used)
667 sourcefilename+sourcefilename
669 +/b[tests show windows then ignores to end of parameter]
672 if (*thisparam
=='+') {
673 if (lastcopyentry
== NULL
) {
674 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
678 concatnextfilename
= TRUE
;
682 /* Move to next thing to process */
684 if (*thisparam
== 0x00)
685 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
689 /* We have found something to process - build a COPY_FILE block to store it */
690 thiscopy
= heap_alloc(sizeof(COPY_FILES
));
692 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam
));
693 thiscopy
->concatenate
= concatnextfilename
;
694 thiscopy
->binarycopy
= binarymode
;
695 thiscopy
->next
= NULL
;
697 /* Time to work out the name. Allocate at least enough space (deliberately too much to
698 leave space to append \* to the end) , then copy in character by character. Strip off
699 quotes if we find them. */
700 len
= strlenW(thisparam
) + (sizeof(WCHAR
) * 5); /* 5 spare characters, null + \*.* */
701 thiscopy
->name
= heap_alloc(len
*sizeof(WCHAR
));
702 memset(thiscopy
->name
, 0x00, len
);
705 pos2
= thiscopy
->name
;
707 while (*pos1
&& (inquotes
|| (*pos1
!= '+' && *pos1
!= '/'))) {
709 inquotes
= !inquotes
;
711 } else *pos2
++ = *pos1
++;
714 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy
->name
));
716 /* This is either the first source, concatenated subsequent source or destination */
717 if (sourcelist
== NULL
) {
718 WINE_TRACE("Adding as first source part\n");
719 sourcelist
= thiscopy
;
720 lastcopyentry
= thiscopy
;
721 } else if (concatnextfilename
) {
722 WINE_TRACE("Adding to source file list to be concatenated\n");
723 lastcopyentry
->next
= thiscopy
;
724 lastcopyentry
= thiscopy
;
725 } else if (destination
== NULL
) {
726 destination
= thiscopy
;
728 /* We have processed sources and destinations and still found more to do - invalid */
729 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
733 concatnextfilename
= FALSE
;
735 /* We either need to process the rest of the parameter or move to the next */
736 if (*pos1
== '/' || *pos1
== '+') {
740 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
744 /* Ensure we have at least one source file */
746 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
751 /* Default whether automatic overwriting is on. If we are interactive then
752 we prompt by default, otherwise we overwrite by default
753 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
754 if (opt_noty
) prompt
= TRUE
;
755 else if (opt_y
) prompt
= FALSE
;
757 /* By default, we will force the overwrite in batch mode and ask for
758 * confirmation in interactive mode. */
759 prompt
= interactive
;
760 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
761 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
762 * default behavior. */
763 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
764 if (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))) {
765 if (!lstrcmpiW (copycmd
, parmY
))
767 else if (!lstrcmpiW (copycmd
, parmNoY
))
772 /* Calculate the destination now - if none supplied, its current dir +
773 filename of first file in list*/
774 if (destination
== NULL
) {
776 WINE_TRACE("No destination supplied, so need to calculate it\n");
777 strcpyW(destname
, dotW
);
778 strcatW(destname
, slashW
);
780 destination
= heap_alloc(sizeof(COPY_FILES
));
781 if (destination
== NULL
) goto exitreturn
;
782 destination
->concatenate
= FALSE
; /* Not used for destination */
783 destination
->binarycopy
= binarymode
;
784 destination
->next
= NULL
; /* Not used for destination */
785 destination
->name
= NULL
; /* To be filled in */
786 destisdirectory
= TRUE
;
792 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
794 /* Convert to fully qualified path/filename */
795 GetFullPathNameW(destination
->name
, sizeof(destname
)/sizeof(WCHAR
), destname
, &filenamepart
);
796 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname
));
798 /* If parameter is a directory, ensure it ends in \ */
799 attributes
= GetFileAttributesW(destname
);
800 if ((destname
[strlenW(destname
) - 1] == '\\') ||
801 ((attributes
!= INVALID_FILE_ATTRIBUTES
) &&
802 (attributes
& FILE_ATTRIBUTE_DIRECTORY
))) {
804 destisdirectory
= TRUE
;
805 if (!(destname
[strlenW(destname
) - 1] == '\\')) strcatW(destname
, slashW
);
806 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname
));
810 /* Normally, the destination is the current directory unless we are
811 concatenating, in which case its current directory plus first filename.
813 In addition by default it is a binary copy unless concatenating, when
814 the copy defaults to an ascii copy (stop at EOF). We do not know the
815 first source part yet (until we search) so flag as needing filling in. */
818 /* We have found an a+b type syntax, so destination has to be a filename
819 and we need to default to ascii copying. If we have been supplied a
820 directory as the destination, we need to defer calculating the name */
821 if (destisdirectory
) appendfirstsource
= TRUE
;
822 if (destination
->binarycopy
== -1) destination
->binarycopy
= 0;
824 } else if (!destisdirectory
) {
825 /* We have been asked to copy to a filename. Default to ascii IF the
826 source contains wildcards (true even if only one match) */
827 if (strpbrkW(sourcelist
->name
, wildcardsW
) != NULL
) {
828 anyconcats
= TRUE
; /* We really are concatenating to a single file */
829 if (destination
->binarycopy
== -1) {
830 destination
->binarycopy
= 0;
833 if (destination
->binarycopy
== -1) {
834 destination
->binarycopy
= 1;
839 /* Save away the destination name*/
840 heap_free(destination
->name
);
841 destination
->name
= heap_strdupW(destname
);
842 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
843 wine_dbgstr_w(destname
), appendfirstsource
);
845 /* Remember if the destination is a device */
846 if (strncmpW(destination
->name
, deviceW
, strlenW(deviceW
)) == 0) {
847 WINE_TRACE("Destination is a device\n");
851 /* Now we need to walk the set of sources, and process each name we come to.
852 If anyconcats is true, we are writing to one file, otherwise we are using
853 the source name each time.
854 If destination exists, prompt for overwrite the first time (if concatenating
855 we ask each time until yes is answered)
856 The first source file we come across must exist (when wildcards expanded)
857 and if concatenating with overwrite prompts, each source file must exist
858 until a yes is answered. */
860 thiscopy
= sourcelist
;
863 while (thiscopy
!= NULL
) {
865 WCHAR srcpath
[MAX_PATH
];
866 const WCHAR
*srcname
;
869 BOOL srcisdevice
= FALSE
;
871 /* If it was not explicit, we now know whether we are concatenating or not and
872 hence whether to copy as binary or ascii */
873 if (thiscopy
->binarycopy
== -1) thiscopy
->binarycopy
= !anyconcats
;
875 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
876 to where the filename portion begins (used for wildcart expansion. */
877 GetFullPathNameW(thiscopy
->name
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, &filenamepart
);
878 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath
));
880 /* If parameter is a directory, ensure it ends in \* */
881 attributes
= GetFileAttributesW(srcpath
);
882 if (srcpath
[strlenW(srcpath
) - 1] == '\\') {
884 /* We need to know where the filename part starts, so append * and
885 recalculate the full resulting path */
886 strcatW(thiscopy
->name
, starW
);
887 GetFullPathNameW(thiscopy
->name
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, &filenamepart
);
888 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath
));
890 } else if ((strpbrkW(srcpath
, wildcardsW
) == NULL
) &&
891 (attributes
!= INVALID_FILE_ATTRIBUTES
) &&
892 (attributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
894 /* We need to know where the filename part starts, so append \* and
895 recalculate the full resulting path */
896 strcatW(thiscopy
->name
, slashstarW
);
897 GetFullPathNameW(thiscopy
->name
, sizeof(srcpath
)/sizeof(WCHAR
), srcpath
, &filenamepart
);
898 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath
));
901 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
902 wine_dbgstr_w(srcpath
), anyconcats
);
904 /* If the source is a device, just use it, otherwise search */
905 if (strncmpW(srcpath
, deviceW
, strlenW(deviceW
)) == 0) {
906 WINE_TRACE("Source is a device\n");
908 srcname
= &srcpath
[4]; /* After the \\.\ prefix */
911 /* Loop through all source files */
912 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath
));
913 hff
= FindFirstFileW(srcpath
, &fd
);
914 if (hff
!= INVALID_HANDLE_VALUE
) {
915 srcname
= fd
.cFileName
;
919 if (srcisdevice
|| hff
!= INVALID_HANDLE_VALUE
) {
921 WCHAR outname
[MAX_PATH
];
924 /* Skip . and .., and directories */
925 if (!srcisdevice
&& fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
926 WINE_TRACE("Skipping directories\n");
929 /* Build final destination name */
930 strcpyW(outname
, destination
->name
);
931 if (destisdirectory
|| appendfirstsource
) strcatW(outname
, srcname
);
933 /* Build source name */
934 if (!srcisdevice
) strcpyW(filenamepart
, srcname
);
936 /* Do we just overwrite (we do if we are writing to a device) */
938 if (dstisdevice
|| (anyconcats
&& writtenoneconcat
)) {
942 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath
));
943 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname
));
944 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
945 thiscopy
->binarycopy
, destination
->binarycopy
, overwrite
, prompt
);
947 /* Prompt before overwriting */
949 DWORD attributes
= GetFileAttributesW(outname
);
950 if (attributes
!= INVALID_FILE_ATTRIBUTES
) {
952 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), outname
);
953 overwrite
= WCMD_ask_confirm(question
, FALSE
, NULL
);
956 else overwrite
= TRUE
;
959 /* If we needed to save away the first filename, do it */
960 if (appendfirstsource
&& overwrite
) {
961 heap_free(destination
->name
);
962 destination
->name
= heap_strdupW(outname
);
963 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname
));
964 appendfirstsource
= FALSE
;
965 destisdirectory
= FALSE
;
968 /* Do the copy as appropriate */
970 if (anyconcats
&& writtenoneconcat
) {
971 if (thiscopy
->binarycopy
) {
972 status
= WCMD_ManualCopy(srcpath
, outname
, FALSE
, TRUE
);
974 status
= WCMD_ManualCopy(srcpath
, outname
, TRUE
, TRUE
);
976 } else if (!thiscopy
->binarycopy
) {
977 status
= WCMD_ManualCopy(srcpath
, outname
, TRUE
, FALSE
);
978 } else if (srcisdevice
) {
979 status
= WCMD_ManualCopy(srcpath
, outname
, FALSE
, FALSE
);
981 status
= CopyFileW(srcpath
, outname
, FALSE
);
987 WINE_TRACE("Copied successfully\n");
988 if (anyconcats
) writtenoneconcat
= TRUE
;
990 /* Append EOF if ascii destination and we are not going to add more onto the end
991 Note: Testing shows windows has an optimization whereas if you have a binary
992 copy of a file to a single destination (ie concatenation) then it does not add
993 the EOF, hence the check on the source copy type below. */
994 if (!destination
->binarycopy
&& !anyconcats
&& !thiscopy
->binarycopy
) {
995 if (!WCMD_AppendEOF(outname
)) {
1003 } while (!srcisdevice
&& FindNextFileW(hff
, &fd
) != 0);
1004 if (!srcisdevice
) FindClose (hff
);
1006 /* Error if the first file was not found */
1007 if (!anyconcats
|| (anyconcats
&& !writtenoneconcat
)) {
1008 WCMD_print_error ();
1013 /* Step on to the next supplied source */
1014 thiscopy
= thiscopy
-> next
;
1017 /* Append EOF if ascii destination and we were concatenating */
1018 if (!errorlevel
&& !destination
->binarycopy
&& anyconcats
&& writtenoneconcat
) {
1019 if (!WCMD_AppendEOF(destination
->name
)) {
1020 WCMD_print_error ();
1025 /* Exit out of the routine, freeing any remaining allocated memory */
1028 thiscopy
= sourcelist
;
1029 while (thiscopy
!= NULL
) {
1030 prevcopy
= thiscopy
;
1031 /* Free up this block*/
1032 thiscopy
= thiscopy
-> next
;
1033 heap_free(prevcopy
->name
);
1034 heap_free(prevcopy
);
1037 /* Free up the destination memory */
1039 heap_free(destination
->name
);
1040 heap_free(destination
);
1046 /****************************************************************************
1049 * Create a directory (and, if needed, any intermediate directories).
1051 * Modifies its argument by replacing slashes temporarily with nulls.
1054 static BOOL
create_full_path(WCHAR
* path
)
1058 /* don't mess with drive letter portion of path, if any */
1063 /* Strip trailing slashes. */
1064 for (p
= path
+ strlenW(path
) - 1; p
!= start
&& *p
== '\\'; p
--)
1067 /* Step through path, creating intermediate directories as needed. */
1068 /* First component includes drive letter, if any. */
1072 /* Skip to end of component */
1073 while (*p
== '\\') p
++;
1074 while (*p
&& *p
!= '\\') p
++;
1076 /* path is now the original full path */
1077 return CreateDirectoryW(path
, NULL
);
1079 /* Truncate path, create intermediate directory, and restore path */
1081 rv
= CreateDirectoryW(path
, NULL
);
1083 if (!rv
&& GetLastError() != ERROR_ALREADY_EXISTS
)
1090 void WCMD_create_dir (WCHAR
*args
) {
1094 if (param1
[0] == 0x00) {
1095 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1098 /* Loop through all args */
1100 WCHAR
*thisArg
= WCMD_parameter(args
, argno
++, &argN
, FALSE
, FALSE
);
1102 if (!create_full_path(thisArg
)) {
1103 WCMD_print_error ();
1109 /* Parse the /A options given by the user on the commandline
1110 * into a bitmask of wanted attributes (*wantSet),
1111 * and a bitmask of unwanted attributes (*wantClear).
1113 static void WCMD_delete_parse_attributes(DWORD
*wantSet
, DWORD
*wantClear
) {
1114 static const WCHAR parmA
[] = {'/','A','\0'};
1117 /* both are strictly 'out' parameters */
1121 /* For each /A argument */
1122 for (p
=strstrW(quals
, parmA
); p
!= NULL
; p
=strstrW(p
, parmA
)) {
1123 /* Skip /A itself */
1126 /* Skip optional : */
1129 /* For each of the attribute specifier chars to this /A option */
1130 for (; *p
!= 0 && *p
!= '/'; p
++) {
1131 BOOL negate
= FALSE
;
1139 /* Convert the attribute specifier to a bit in one of the masks */
1141 case 'R': mask
= FILE_ATTRIBUTE_READONLY
; break;
1142 case 'H': mask
= FILE_ATTRIBUTE_HIDDEN
; break;
1143 case 'S': mask
= FILE_ATTRIBUTE_SYSTEM
; break;
1144 case 'A': mask
= FILE_ATTRIBUTE_ARCHIVE
; break;
1146 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
1156 /* If filename part of parameter is * or *.*,
1157 * and neither /Q nor /P options were given,
1158 * prompt the user whether to proceed.
1159 * Returns FALSE if user says no, TRUE otherwise.
1160 * *pPrompted is set to TRUE if the user is prompted.
1161 * (If /P supplied, del will prompt for individual files later.)
1163 static BOOL
WCMD_delete_confirm_wildcard(const WCHAR
*filename
, BOOL
*pPrompted
) {
1164 static const WCHAR parmP
[] = {'/','P','\0'};
1165 static const WCHAR parmQ
[] = {'/','Q','\0'};
1167 if ((strstrW(quals
, parmQ
) == NULL
) && (strstrW(quals
, parmP
) == NULL
)) {
1168 static const WCHAR anyExt
[]= {'.','*','\0'};
1170 WCHAR dir
[MAX_PATH
];
1171 WCHAR fname
[MAX_PATH
];
1172 WCHAR ext
[MAX_PATH
];
1173 WCHAR fpath
[MAX_PATH
];
1175 /* Convert path into actual directory spec */
1176 GetFullPathNameW(filename
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
1177 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
1179 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1180 if ((strcmpW(fname
, starW
) == 0) &&
1181 (*ext
== 0x00 || (strcmpW(ext
, anyExt
) == 0))) {
1183 WCHAR question
[MAXSTRING
];
1184 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1186 /* Caller uses this to suppress "file not found" warning later */
1189 /* Ask for confirmation */
1190 wsprintfW(question
, fmt
, fpath
);
1191 return WCMD_ask_confirm(question
, TRUE
, NULL
);
1194 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1198 /* Helper function for WCMD_delete().
1199 * Deletes a single file, directory, or wildcard.
1200 * If /S was given, does it recursively.
1201 * Returns TRUE if a file was deleted.
1203 static BOOL
WCMD_delete_one (const WCHAR
*thisArg
) {
1205 static const WCHAR parmP
[] = {'/','P','\0'};
1206 static const WCHAR parmS
[] = {'/','S','\0'};
1207 static const WCHAR parmF
[] = {'/','F','\0'};
1209 DWORD unwanted_attrs
;
1211 WCHAR argCopy
[MAX_PATH
];
1212 WIN32_FIND_DATAW fd
;
1214 WCHAR fpath
[MAX_PATH
];
1216 BOOL handleParm
= TRUE
;
1218 WCMD_delete_parse_attributes(&wanted_attrs
, &unwanted_attrs
);
1220 strcpyW(argCopy
, thisArg
);
1221 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1222 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
1224 if (!WCMD_delete_confirm_wildcard(argCopy
, &found
)) {
1225 /* Skip this arg if user declines to delete *.* */
1229 /* First, try to delete in the current directory */
1230 hff
= FindFirstFileW(argCopy
, &fd
);
1231 if (hff
== INVALID_HANDLE_VALUE
) {
1237 /* Support del <dirname> by just deleting all files dirname\* */
1239 && (strchrW(argCopy
,'*') == NULL
)
1240 && (strchrW(argCopy
,'?') == NULL
)
1241 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
1243 WCHAR modifiedParm
[MAX_PATH
];
1244 static const WCHAR slashStar
[] = {'\\','*','\0'};
1246 strcpyW(modifiedParm
, argCopy
);
1247 strcatW(modifiedParm
, slashStar
);
1250 WCMD_delete_one(modifiedParm
);
1252 } else if (handleParm
) {
1254 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1255 strcpyW (fpath
, argCopy
);
1257 p
= strrchrW (fpath
, '\\');
1260 strcatW (fpath
, fd
.cFileName
);
1262 else strcpyW (fpath
, fd
.cFileName
);
1263 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
1266 /* Handle attribute matching (/A) */
1267 ok
= ((fd
.dwFileAttributes
& wanted_attrs
) == wanted_attrs
)
1268 && ((fd
.dwFileAttributes
& unwanted_attrs
) == 0);
1270 /* /P means prompt for each file */
1271 if (ok
&& strstrW (quals
, parmP
) != NULL
) {
1274 /* Ask for confirmation */
1275 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
1276 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1277 LocalFree(question
);
1280 /* Only proceed if ok to */
1283 /* If file is read only, and /A:r or /F supplied, delete it */
1284 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
1285 ((wanted_attrs
& FILE_ATTRIBUTE_READONLY
) ||
1286 strstrW (quals
, parmF
) != NULL
)) {
1287 SetFileAttributesW(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
1290 /* Now do the delete */
1291 if (!DeleteFileW(fpath
)) WCMD_print_error ();
1295 } while (FindNextFileW(hff
, &fd
) != 0);
1299 /* Now recurse into all subdirectories handling the parameter in the same way */
1300 if (strstrW (quals
, parmS
) != NULL
) {
1302 WCHAR thisDir
[MAX_PATH
];
1306 WCHAR dir
[MAX_PATH
];
1307 WCHAR fname
[MAX_PATH
];
1308 WCHAR ext
[MAX_PATH
];
1310 /* Convert path into actual directory spec */
1311 GetFullPathNameW(argCopy
, sizeof(thisDir
)/sizeof(WCHAR
), thisDir
, NULL
);
1312 WCMD_splitpath(thisDir
, drive
, dir
, fname
, ext
);
1314 strcpyW(thisDir
, drive
);
1315 strcatW(thisDir
, dir
);
1316 cPos
= strlenW(thisDir
);
1318 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
1320 /* Append '*' to the directory */
1321 thisDir
[cPos
] = '*';
1322 thisDir
[cPos
+1] = 0x00;
1324 hff
= FindFirstFileW(thisDir
, &fd
);
1326 /* Remove residual '*' */
1327 thisDir
[cPos
] = 0x00;
1329 if (hff
!= INVALID_HANDLE_VALUE
) {
1330 DIRECTORY_STACK
*allDirs
= NULL
;
1331 DIRECTORY_STACK
*lastEntry
= NULL
;
1334 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
1335 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
1336 (strcmpW(fd
.cFileName
, dotW
) != 0)) {
1338 DIRECTORY_STACK
*nextDir
;
1339 WCHAR subParm
[MAX_PATH
];
1341 /* Work out search parameter in sub dir */
1342 strcpyW (subParm
, thisDir
);
1343 strcatW (subParm
, fd
.cFileName
);
1344 strcatW (subParm
, slashW
);
1345 strcatW (subParm
, fname
);
1346 strcatW (subParm
, ext
);
1347 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
1349 /* Allocate memory, add to list */
1350 nextDir
= heap_alloc(sizeof(DIRECTORY_STACK
));
1351 if (allDirs
== NULL
) allDirs
= nextDir
;
1352 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
1353 lastEntry
= nextDir
;
1354 nextDir
->next
= NULL
;
1355 nextDir
->dirName
= heap_strdupW(subParm
);
1357 } while (FindNextFileW(hff
, &fd
) != 0);
1360 /* Go through each subdir doing the delete */
1361 while (allDirs
!= NULL
) {
1362 DIRECTORY_STACK
*tempDir
;
1364 tempDir
= allDirs
->next
;
1365 found
|= WCMD_delete_one (allDirs
->dirName
);
1367 heap_free(allDirs
->dirName
);
1377 /****************************************************************************
1380 * Delete a file or wildcarded set.
1383 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1384 * - Each set is a pattern, eg /ahr /as-r means
1385 * readonly+hidden OR nonreadonly system files
1386 * - The '-' applies to a single field, ie /a:-hr means read only
1390 BOOL
WCMD_delete (WCHAR
*args
) {
1393 BOOL argsProcessed
= FALSE
;
1394 BOOL foundAny
= FALSE
;
1398 for (argno
=0; ; argno
++) {
1403 thisArg
= WCMD_parameter (args
, argno
, &argN
, FALSE
, FALSE
);
1405 break; /* no more parameters */
1407 continue; /* skip options */
1409 argsProcessed
= TRUE
;
1410 found
= WCMD_delete_one(thisArg
);
1413 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND
), thisArg
);
1418 /* Handle no valid args */
1420 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1428 * Returns a trimmed version of s with all leading and trailing whitespace removed
1432 static WCHAR
*WCMD_strtrim(const WCHAR
*s
)
1434 DWORD len
= strlenW(s
);
1435 const WCHAR
*start
= s
;
1438 result
= heap_alloc((len
+ 1) * sizeof(WCHAR
));
1440 while (isspaceW(*start
)) start
++;
1442 const WCHAR
*end
= s
+ len
- 1;
1443 while (end
> start
&& isspaceW(*end
)) end
--;
1444 memcpy(result
, start
, (end
- start
+ 2) * sizeof(WCHAR
));
1445 result
[end
- start
+ 1] = '\0';
1453 /****************************************************************************
1456 * Echo input to the screen (or not). We don't try to emulate the bugs
1457 * in DOS (try typing "ECHO ON AGAIN" for an example).
1460 void WCMD_echo (const WCHAR
*args
)
1463 const WCHAR
*origcommand
= args
;
1466 if ( args
[0]==' ' || args
[0]=='\t' || args
[0]=='.'
1467 || args
[0]==':' || args
[0]==';')
1470 trimmed
= WCMD_strtrim(args
);
1471 if (!trimmed
) return;
1473 count
= strlenW(trimmed
);
1474 if (count
== 0 && origcommand
[0]!='.' && origcommand
[0]!=':'
1475 && origcommand
[0]!=';') {
1476 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
1477 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
1482 if (lstrcmpiW(trimmed
, onW
) == 0)
1484 else if (lstrcmpiW(trimmed
, offW
) == 0)
1487 WCMD_output_asis (args
);
1488 WCMD_output_asis (newlineW
);
1493 /*****************************************************************************
1496 * Execute a command, and any && or bracketed follow on to the command. The
1497 * first command to be executed may not be at the front of the
1498 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1500 static void WCMD_part_execute(CMD_LIST
**cmdList
, const WCHAR
*firstcmd
,
1501 BOOL isIF
, BOOL executecmds
)
1503 CMD_LIST
*curPosition
= *cmdList
;
1504 int myDepth
= (*cmdList
)->bracketDepth
;
1506 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d)\n", cmdList
, wine_dbgstr_w(firstcmd
),
1509 /* Skip leading whitespace between condition and the command */
1510 while (firstcmd
&& *firstcmd
&& (*firstcmd
==' ' || *firstcmd
=='\t')) firstcmd
++;
1512 /* Process the first command, if there is one */
1513 if (executecmds
&& firstcmd
&& *firstcmd
) {
1514 WCHAR
*command
= heap_strdupW(firstcmd
);
1515 WCMD_execute (firstcmd
, (*cmdList
)->redirects
, cmdList
, FALSE
);
1520 /* If it didn't move the position, step to next command */
1521 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1523 /* Process any other parts of the command */
1525 BOOL processThese
= executecmds
;
1528 static const WCHAR ifElse
[] = {'e','l','s','e'};
1530 /* execute all appropriate commands */
1531 curPosition
= *cmdList
;
1533 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1535 (*cmdList
)->prevDelim
,
1536 (*cmdList
)->bracketDepth
, myDepth
);
1538 /* Execute any statements appended to the line */
1539 /* FIXME: Only if previous call worked for && or failed for || */
1540 if ((*cmdList
)->prevDelim
== CMD_ONFAILURE
||
1541 (*cmdList
)->prevDelim
== CMD_ONSUCCESS
) {
1542 if (processThese
&& (*cmdList
)->command
) {
1543 WCMD_execute ((*cmdList
)->command
, (*cmdList
)->redirects
,
1546 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1548 /* Execute any appended to the statement with (...) */
1549 } else if ((*cmdList
)->bracketDepth
> myDepth
) {
1551 *cmdList
= WCMD_process_commands(*cmdList
, TRUE
, FALSE
);
1552 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList
);
1554 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1556 /* End of the command - does 'ELSE ' follow as the next command? */
1559 && WCMD_keyword_ws_found(ifElse
, sizeof(ifElse
)/sizeof(ifElse
[0]),
1560 (*cmdList
)->command
)) {
1562 /* Swap between if and else processing */
1563 processThese
= !processThese
;
1565 /* Process the ELSE part */
1567 const int keyw_len
= sizeof(ifElse
)/sizeof(ifElse
[0]) + 1;
1568 WCHAR
*cmd
= ((*cmdList
)->command
) + keyw_len
;
1570 /* Skip leading whitespace between condition and the command */
1571 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
1573 WCMD_execute (cmd
, (*cmdList
)->redirects
, cmdList
, FALSE
);
1576 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1578 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
1587 /*****************************************************************************
1588 * WCMD_parse_forf_options
1590 * Parses the for /f 'options', extracting the values and validating the
1591 * keywords. Note all keywords are optional.
1593 * options [I] The unparsed parameter string
1594 * eol [O] Set to the comment character (eol=x)
1595 * skip [O] Set to the number of lines to skip (skip=xx)
1596 * delims [O] Set to the token delimiters (delims=)
1597 * tokens [O] Set to the requested tokens, as provided (tokens=)
1598 * usebackq [O] Set to TRUE if usebackq found
1600 * Returns TRUE on success, FALSE on syntax error
1603 static BOOL
WCMD_parse_forf_options(WCHAR
*options
, WCHAR
*eol
, int *skip
,
1604 WCHAR
*delims
, WCHAR
*tokens
, BOOL
*usebackq
)
1607 WCHAR
*pos
= options
;
1608 int len
= strlenW(pos
);
1609 static const WCHAR eolW
[] = {'e','o','l','='};
1610 static const WCHAR skipW
[] = {'s','k','i','p','='};
1611 static const WCHAR tokensW
[] = {'t','o','k','e','n','s','='};
1612 static const WCHAR delimsW
[] = {'d','e','l','i','m','s','='};
1613 static const WCHAR usebackqW
[] = {'u','s','e','b','a','c','k','q'};
1614 static const WCHAR forf_defaultdelims
[] = {' ', '\t', '\0'};
1615 static const WCHAR forf_defaulttokens
[] = {'1', '\0'};
1617 /* Initialize to defaults */
1618 strcpyW(delims
, forf_defaultdelims
);
1619 strcpyW(tokens
, forf_defaulttokens
);
1624 /* Strip (optional) leading and trailing quotes */
1625 if ((*pos
== '"') && (pos
[len
-1] == '"')) {
1630 /* Process each keyword */
1631 while (pos
&& *pos
) {
1632 if (*pos
== ' ' || *pos
== '\t') {
1635 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1636 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1637 pos
, sizeof(eolW
)/sizeof(WCHAR
),
1638 eolW
, sizeof(eolW
)/sizeof(WCHAR
)) == CSTR_EQUAL
) {
1639 *eol
= *(pos
+ sizeof(eolW
)/sizeof(WCHAR
));
1640 pos
= pos
+ sizeof(eolW
)/sizeof(WCHAR
) + 1;
1641 WINE_TRACE("Found eol as %c(%x)\n", *eol
, *eol
);
1643 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1644 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1645 pos
, sizeof(skipW
)/sizeof(WCHAR
),
1646 skipW
, sizeof(skipW
)/sizeof(WCHAR
)) == CSTR_EQUAL
) {
1647 WCHAR
*nextchar
= NULL
;
1648 pos
= pos
+ sizeof(skipW
)/sizeof(WCHAR
);
1649 *skip
= strtoulW(pos
, &nextchar
, 0);
1650 WINE_TRACE("Found skip as %d lines\n", *skip
);
1653 /* Save if usebackq semantics are in effect */
1654 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1655 pos
, sizeof(usebackqW
)/sizeof(WCHAR
),
1656 usebackqW
, sizeof(usebackqW
)/sizeof(WCHAR
)) == CSTR_EQUAL
) {
1658 pos
= pos
+ sizeof(usebackqW
)/sizeof(WCHAR
);
1659 WINE_TRACE("Found usebackq\n");
1661 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1662 if you finish the optionsroot string with delims= otherwise the space is
1663 just a token delimiter! */
1664 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1665 pos
, sizeof(delimsW
)/sizeof(WCHAR
),
1666 delimsW
, sizeof(delimsW
)/sizeof(WCHAR
)) == CSTR_EQUAL
) {
1669 pos
= pos
+ sizeof(delimsW
)/sizeof(WCHAR
);
1670 while (*pos
&& *pos
!= ' ') {
1674 if (*pos
==' ' && *(pos
+1)==0) delims
[i
++] = *pos
;
1675 delims
[i
++] = 0; /* Null terminate the delims */
1676 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims
));
1678 /* Save the tokens being requested */
1679 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1680 pos
, sizeof(tokensW
)/sizeof(WCHAR
),
1681 tokensW
, sizeof(tokensW
)/sizeof(WCHAR
)) == CSTR_EQUAL
) {
1684 pos
= pos
+ sizeof(tokensW
)/sizeof(WCHAR
);
1685 while (*pos
&& *pos
!= ' ') {
1689 tokens
[i
++] = 0; /* Null terminate the tokens */
1690 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens
));
1693 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos
));
1700 /*****************************************************************************
1701 * WCMD_add_dirstowalk
1703 * When recursing through directories (for /r), we need to add to the list of
1704 * directories still to walk, any subdirectories of the one we are processing.
1707 * options [I] The remaining list of directories still to process
1709 * Note this routine inserts the subdirectories found between the entry being
1710 * processed, and any other directory still to be processed, mimicing what
1713 static void WCMD_add_dirstowalk(DIRECTORY_STACK
*dirsToWalk
) {
1714 DIRECTORY_STACK
*remainingDirs
= dirsToWalk
;
1715 WCHAR fullitem
[MAX_PATH
];
1716 WIN32_FIND_DATAW fd
;
1719 /* Build a generic search and add all directories on the list of directories
1721 strcpyW(fullitem
, dirsToWalk
->dirName
);
1722 strcatW(fullitem
, slashstarW
);
1723 hff
= FindFirstFileW(fullitem
, &fd
);
1724 if (hff
!= INVALID_HANDLE_VALUE
) {
1726 WINE_TRACE("Looking for subdirectories\n");
1727 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
1728 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
1729 (strcmpW(fd
.cFileName
, dotW
) != 0))
1731 /* Allocate memory, add to list */
1732 DIRECTORY_STACK
*toWalk
= heap_alloc(sizeof(DIRECTORY_STACK
));
1733 WINE_TRACE("(%p->%p)\n", remainingDirs
, remainingDirs
->next
);
1734 toWalk
->next
= remainingDirs
->next
;
1735 remainingDirs
->next
= toWalk
;
1736 remainingDirs
= toWalk
;
1737 toWalk
->dirName
= heap_alloc(sizeof(WCHAR
) * (strlenW(dirsToWalk
->dirName
) + 2 + strlenW(fd
.cFileName
)));
1738 strcpyW(toWalk
->dirName
, dirsToWalk
->dirName
);
1739 strcatW(toWalk
->dirName
, slashW
);
1740 strcatW(toWalk
->dirName
, fd
.cFileName
);
1741 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk
->dirName
),
1742 toWalk
, toWalk
->next
);
1744 } while (FindNextFileW(hff
, &fd
) != 0);
1745 WINE_TRACE("Finished adding all subdirectories\n");
1750 /**************************************************************************
1751 * WCMD_for_nexttoken
1753 * Parse the token= line, identifying the next highest number not processed
1754 * so far. Count how many tokens are referred (including duplicates) and
1755 * optionally return that, plus optionally indicate if the tokens= line
1759 * lasttoken [I] - Identifies the token index of the last one
1760 * returned so far (-1 used for first loop)
1761 * tokenstr [I] - The specified tokens= line
1762 * firstCmd [O] - Optionally indicate how many tokens are listed
1763 * doAll [O] - Optionally indicate if line ends with *
1764 * duplicates [O] - Optionally indicate if there is any evidence of
1765 * overlaying tokens in the string
1766 * Note the caller should keep a running track of duplicates as the tokens
1767 * are recursively passed. If any have duplicates, then the * token should
1770 static int WCMD_for_nexttoken(int lasttoken
, WCHAR
*tokenstr
,
1771 int *totalfound
, BOOL
*doall
,
1774 WCHAR
*pos
= tokenstr
;
1777 if (totalfound
) *totalfound
= 0;
1778 if (doall
) *doall
= FALSE
;
1779 if (duplicates
) *duplicates
= FALSE
;
1781 WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken
,
1782 wine_dbgstr_w(tokenstr
), nexttoken
);
1784 /* Loop through the token string, parsing it. Valid syntax is:
1785 token=m or x-y with comma delimiter and optionally * to finish*/
1787 int nextnumber1
, nextnumber2
= -1;
1790 /* Get the next number */
1791 nextnumber1
= strtoulW(pos
, &nextchar
, 10);
1793 /* If it is followed by a minus, its a range, so get the next one as well */
1794 if (*nextchar
== '-') {
1795 nextnumber2
= strtoulW(nextchar
+1, &nextchar
, 10);
1797 /* We want to return the lowest number that is higher than lasttoken
1798 but only if range is positive */
1799 if (nextnumber2
>= nextnumber1
&&
1800 lasttoken
< nextnumber2
) {
1803 if (nexttoken
== -1) {
1804 nextvalue
= max(nextnumber1
, (lasttoken
+1));
1806 nextvalue
= min(nexttoken
, max(nextnumber1
, (lasttoken
+1)));
1809 /* Flag if duplicates identified */
1810 if (nexttoken
== nextvalue
&& duplicates
) *duplicates
= TRUE
;
1812 nexttoken
= nextvalue
;
1815 /* Update the running total for the whole range */
1816 if (nextnumber2
>= nextnumber1
&& totalfound
) {
1817 *totalfound
= *totalfound
+ 1 + (nextnumber2
- nextnumber1
);
1821 if (totalfound
) (*totalfound
)++;
1823 /* See if the number found is one we have already seen */
1824 if (nextnumber1
== nexttoken
&& duplicates
) *duplicates
= TRUE
;
1826 /* We want to return the lowest number that is higher than lasttoken */
1827 if (lasttoken
< nextnumber1
&&
1828 ((nexttoken
== -1) || (nextnumber1
< nexttoken
))) {
1829 nexttoken
= nextnumber1
;
1834 /* Remember if it is followed by a star, and if it is indicate a need to
1835 show all tokens, unless a duplicate has been found */
1836 if (*nextchar
== '*') {
1837 if (doall
) *doall
= TRUE
;
1838 if (totalfound
) (*totalfound
)++;
1841 /* Step on to the next character */
1847 if (nexttoken
== -1) nexttoken
= lasttoken
;
1848 WINE_TRACE("Found next token after %d was %d\n", lasttoken
, nexttoken
);
1849 if (totalfound
) WINE_TRACE("Found total tokens in total %d\n", *totalfound
);
1850 if (doall
&& *doall
) WINE_TRACE("Request for all tokens found\n");
1851 if (duplicates
&& *duplicates
) WINE_TRACE("Duplicate numbers found\n");
1855 /**************************************************************************
1858 * When parsing file or string contents (for /f), once the string to parse
1859 * has been identified, handle the various options and call the do part
1863 * cmdStart [I] - Identifies the list of commands making up the
1864 * for loop body (especially if brackets in use)
1865 * firstCmd [I] - The textual start of the command after the DO
1866 * which is within the first item of cmdStart
1867 * cmdEnd [O] - Identifies where to continue after the DO
1868 * variable [I] - The variable identified on the for line
1869 * buffer [I] - The string to parse
1870 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1871 * forf_skip [I/O] - How many lines to skip first
1872 * forf_eol [I] - The 'end of line' (comment) character
1873 * forf_delims [I] - The delimiters to use when breaking the string apart
1874 * forf_tokens [I] - The tokens to use when breaking the string apart
1876 static void WCMD_parse_line(CMD_LIST
*cmdStart
,
1877 const WCHAR
*firstCmd
,
1879 const WCHAR variable
,
1885 WCHAR
*forf_tokens
) {
1888 FOR_CONTEXT oldcontext
;
1889 int varidx
, varoffset
;
1890 int nexttoken
, lasttoken
= -1;
1891 BOOL starfound
= FALSE
;
1892 BOOL thisduplicate
= FALSE
;
1893 BOOL anyduplicates
= FALSE
;
1896 /* Skip lines if requested */
1902 /* Save away any existing for variable context (e.g. nested for loops) */
1903 oldcontext
= forloopcontext
;
1905 /* Extract the parameters based on the tokens= value (There will always
1906 be some value, as if it is not supplied, it defaults to tokens=1).
1908 Count how many tokens are named in the line, identify the lowest
1909 Empty (set to null terminated string) that number of named variables
1910 While lasttoken != nextlowest
1911 %letter = parameter number 'nextlowest'
1912 letter++ (if >26 or >52 abort)
1913 Go through token= string finding next lowest number
1914 If token ends in * set %letter = raw position of token(nextnumber+1)
1917 nexttoken
= WCMD_for_nexttoken(lasttoken
, forf_tokens
, &totalfound
,
1918 NULL
, &thisduplicate
);
1919 varidx
= FOR_VAR_IDX(variable
);
1921 /* Empty out variables */
1923 varidx
>= 0 && varoffset
<totalfound
&& ((varidx
+varoffset
)%26);
1925 forloopcontext
.variable
[varidx
+ varoffset
] = (WCHAR
*)nullW
;
1926 /* Stop if we walk beyond z or Z */
1927 if (((varidx
+varoffset
) % 26) == 0) break;
1930 /* Loop extracting the tokens */
1932 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer
));
1933 while (varidx
>= 0 && (nexttoken
> lasttoken
)) {
1934 anyduplicates
|= thisduplicate
;
1936 /* Extract the token number requested and set into the next variable context */
1937 parm
= WCMD_parameter_with_delims(buffer
, (nexttoken
-1), NULL
, FALSE
, FALSE
, forf_delims
);
1938 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken
,
1939 varidx
+ varoffset
, wine_dbgstr_w(parm
));
1941 forloopcontext
.variable
[varidx
+ varoffset
] = heap_strdupW(parm
);
1943 if (((varidx
+ varoffset
) %26) == 0) break;
1946 /* Find the next token */
1947 lasttoken
= nexttoken
;
1948 nexttoken
= WCMD_for_nexttoken(lasttoken
, forf_tokens
, NULL
,
1949 &starfound
, &thisduplicate
);
1952 /* If all the rest of the tokens were requested, and there is still space in
1953 the variable range, write them now */
1954 if (!anyduplicates
&& starfound
&& varidx
>= 0 && ((varidx
+varoffset
) % 26)) {
1956 WCMD_parameter_with_delims(buffer
, (nexttoken
-1), &parm
, FALSE
, FALSE
, forf_delims
);
1957 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
1958 varidx
+ varoffset
, wine_dbgstr_w(parm
));
1959 forloopcontext
.variable
[varidx
+ varoffset
] = heap_strdupW(parm
);
1962 /* Execute the body of the foor loop with these values */
1963 if (forloopcontext
.variable
[varidx
] && forloopcontext
.variable
[varidx
][0] != forf_eol
) {
1964 CMD_LIST
*thisCmdStart
= cmdStart
;
1966 WCMD_part_execute(&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
1967 *cmdEnd
= thisCmdStart
;
1970 /* Free the duplicated strings, and restore the context */
1973 for (i
=varidx
; i
<MAX_FOR_VARIABLES
; i
++) {
1974 if ((forloopcontext
.variable
[i
] != oldcontext
.variable
[i
]) &&
1975 (forloopcontext
.variable
[i
] != nullW
)) {
1976 heap_free(forloopcontext
.variable
[i
]);
1981 /* Restore the original for variable contextx */
1982 forloopcontext
= oldcontext
;
1985 /**************************************************************************
1986 * WCMD_forf_getinputhandle
1988 * Return a file handle which can be used for reading the input lines,
1989 * either to a specific file (which may be quote delimited as we have to
1990 * read the parameters in raw mode) or to a command which we need to
1991 * execute. The command being executed runs in its own shell and stores
1992 * its data in a temporary file.
1995 * usebackq [I] - Indicates whether usebackq is in effect or not
1996 * itemStr [I] - The item to be handled, either a filename or
1997 * whole command string to execute
1998 * iscmd [I] - Identifies whether this is a command or not
2000 * Returns a file handle which can be used to read the input lines from.
2002 static HANDLE
WCMD_forf_getinputhandle(BOOL usebackq
, WCHAR
*itemstr
, BOOL iscmd
) {
2003 WCHAR temp_str
[MAX_PATH
];
2004 WCHAR temp_file
[MAX_PATH
];
2005 WCHAR temp_cmd
[MAXSTRING
];
2006 HANDLE hinput
= INVALID_HANDLE_VALUE
;
2007 static const WCHAR redirOutW
[] = {'>','%','s','\0'};
2008 static const WCHAR cmdW
[] = {'C','M','D','\0'};
2009 static const WCHAR cmdslashcW
[] = {'C','M','D','.','E','X','E',' ',
2010 '/','C',' ','"','%','s','"','\0'};
2012 /* Remove leading and trailing character */
2013 if ((iscmd
&& (itemstr
[0] == '`' && usebackq
)) ||
2014 (iscmd
&& (itemstr
[0] == '\'' && !usebackq
)) ||
2015 (!iscmd
&& (itemstr
[0] == '"' && usebackq
)))
2017 itemstr
[strlenW(itemstr
)-1] = 0x00;
2022 /* Get temp filename */
2023 GetTempPathW(sizeof(temp_str
)/sizeof(WCHAR
), temp_str
);
2024 GetTempFileNameW(temp_str
, cmdW
, 0, temp_file
);
2026 /* Redirect output to the temporary file */
2027 wsprintfW(temp_str
, redirOutW
, temp_file
);
2028 wsprintfW(temp_cmd
, cmdslashcW
, itemstr
);
2029 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2030 wine_dbgstr_w(temp_cmd
), wine_dbgstr_w(temp_str
));
2031 WCMD_execute (temp_cmd
, temp_str
, NULL
, FALSE
);
2033 /* Open the file, read line by line and process */
2034 hinput
= CreateFileW(temp_file
, GENERIC_READ
, FILE_SHARE_READ
,
2035 NULL
, OPEN_EXISTING
, FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
2038 /* Open the file, read line by line and process */
2039 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr
));
2040 hinput
= CreateFileW(itemstr
, GENERIC_READ
, FILE_SHARE_READ
,
2041 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
2046 /**************************************************************************
2049 * Batch file loop processing.
2051 * On entry: cmdList contains the syntax up to the set
2052 * next cmdList and all in that bracket contain the set data
2053 * next cmdlist contains the DO cmd
2054 * following that is either brackets or && entries (as per if)
2058 void WCMD_for (WCHAR
*p
, CMD_LIST
**cmdList
) {
2060 WIN32_FIND_DATAW fd
;
2063 static const WCHAR inW
[] = {'i','n'};
2064 static const WCHAR doW
[] = {'d','o'};
2065 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
2068 WCHAR
*oldvariablevalue
;
2071 WCHAR optionsRoot
[MAX_PATH
];
2072 DIRECTORY_STACK
*dirsToWalk
= NULL
;
2073 BOOL expandDirs
= FALSE
;
2074 BOOL useNumbers
= FALSE
;
2075 BOOL doFileset
= FALSE
;
2076 BOOL doRecurse
= FALSE
;
2077 BOOL doExecuted
= FALSE
; /* Has the 'do' part been executed */
2078 LONG numbers
[3] = {0,0,0}; /* Defaults to 0 in native */
2080 CMD_LIST
*thisCmdStart
;
2081 int parameterNo
= 0;
2084 WCHAR forf_delims
[256];
2085 WCHAR forf_tokens
[MAXSTRING
];
2086 BOOL forf_usebackq
= FALSE
;
2088 /* Handle optional qualifiers (multiple are allowed) */
2089 WCHAR
*thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2092 while (thisArg
&& *thisArg
== '/') {
2093 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg
));
2095 switch (toupperW(*thisArg
)) {
2096 case 'D': expandDirs
= TRUE
; break;
2097 case 'L': useNumbers
= TRUE
; break;
2099 /* Recursive is special case - /R can have an optional path following it */
2100 /* filenamesets are another special case - /F can have an optional options following it */
2104 /* When recursing directories, use current directory as the starting point unless
2105 subsequently overridden */
2106 doRecurse
= (toupperW(*thisArg
) == 'R');
2107 if (doRecurse
) GetCurrentDirectoryW(sizeof(optionsRoot
)/sizeof(WCHAR
), optionsRoot
);
2109 doFileset
= (toupperW(*thisArg
) == 'F');
2111 /* Retrieve next parameter to see if is root/options (raw form required
2112 with for /f, or unquoted in for /r) */
2113 thisArg
= WCMD_parameter(p
, parameterNo
, NULL
, doFileset
, FALSE
);
2115 /* Next parm is either qualifier, path/options or variable -
2116 only care about it if it is the path/options */
2117 if (thisArg
&& *thisArg
!= '/' && *thisArg
!= '%') {
2119 strcpyW(optionsRoot
, thisArg
);
2124 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg
);
2127 /* Step to next token */
2128 thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2131 /* Ensure line continues with variable */
2132 if (!*thisArg
|| *thisArg
!= '%') {
2133 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2137 /* With for /f parse the options if provided */
2139 if (!WCMD_parse_forf_options(optionsRoot
, &forf_eol
, &forf_skip
,
2140 forf_delims
, forf_tokens
, &forf_usebackq
))
2142 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2146 /* Set up the list of directories to recurse if we are going to */
2147 } else if (doRecurse
) {
2148 /* Allocate memory, add to list */
2149 dirsToWalk
= heap_alloc(sizeof(DIRECTORY_STACK
));
2150 dirsToWalk
->next
= NULL
;
2151 dirsToWalk
->dirName
= heap_strdupW(optionsRoot
);
2152 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk
->dirName
));
2155 /* Variable should follow */
2156 strcpyW(variable
, thisArg
);
2157 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable
));
2158 varidx
= FOR_VAR_IDX(variable
[1]);
2160 /* Ensure line continues with IN */
2161 thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2163 || !(CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2164 thisArg
, sizeof(inW
)/sizeof(inW
[0]), inW
,
2165 sizeof(inW
)/sizeof(inW
[0])) == CSTR_EQUAL
)) {
2166 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2170 /* Save away where the set of data starts and the variable */
2171 thisDepth
= (*cmdList
)->bracketDepth
;
2172 *cmdList
= (*cmdList
)->nextcommand
;
2173 setStart
= (*cmdList
);
2175 /* Skip until the close bracket */
2176 WINE_TRACE("Searching %p as the set\n", *cmdList
);
2178 (*cmdList
)->command
!= NULL
&&
2179 (*cmdList
)->bracketDepth
> thisDepth
) {
2180 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList
);
2181 *cmdList
= (*cmdList
)->nextcommand
;
2184 /* Skip the close bracket, if there is one */
2185 if (*cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
2187 /* Syntax error if missing close bracket, or nothing following it
2188 and once we have the complete set, we expect a DO */
2189 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList
);
2190 if ((*cmdList
== NULL
)
2191 || !WCMD_keyword_ws_found(doW
, sizeof(doW
)/sizeof(doW
[0]), (*cmdList
)->command
)) {
2193 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2199 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2200 mode, or once for the rest of the time. */
2203 /* Save away the starting position for the commands (and offset for the
2205 cmdStart
= *cmdList
;
2206 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
2209 /* If we are recursing directories (ie /R), add all sub directories now, then
2210 prefix the root when searching for the item */
2211 if (dirsToWalk
) WCMD_add_dirstowalk(dirsToWalk
);
2214 /* Loop through all set entries */
2216 thisSet
->command
!= NULL
&&
2217 thisSet
->bracketDepth
>= thisDepth
) {
2219 /* Loop through all entries on the same line */
2222 WCHAR buffer
[MAXSTRING
];
2224 WINE_TRACE("Processing for set %p\n", thisSet
);
2226 while (*(item
= WCMD_parameter (thisSet
->command
, i
, &itemStart
, TRUE
, FALSE
))) {
2229 * If the parameter within the set has a wildcard then search for matching files
2230 * otherwise do a literal substitution.
2232 static const WCHAR wildcards
[] = {'*','?','\0'};
2233 thisCmdStart
= cmdStart
;
2236 WINE_TRACE("Processing for item %d '%s'\n", itemNum
, wine_dbgstr_w(item
));
2238 if (!useNumbers
&& !doFileset
) {
2239 WCHAR fullitem
[MAX_PATH
];
2242 /* Now build the item to use / search for in the specified directory,
2243 as it is fully qualified in the /R case */
2245 strcpyW(fullitem
, dirsToWalk
->dirName
);
2246 strcatW(fullitem
, slashW
);
2247 strcatW(fullitem
, item
);
2249 WCHAR
*prefix
= strrchrW(item
, '\\');
2250 if (prefix
) prefixlen
= (prefix
- item
) + 1;
2251 strcpyW(fullitem
, item
);
2254 if (strpbrkW (fullitem
, wildcards
)) {
2255 hff
= FindFirstFileW(fullitem
, &fd
);
2256 if (hff
!= INVALID_HANDLE_VALUE
) {
2258 BOOL isDirectory
= FALSE
;
2260 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) isDirectory
= TRUE
;
2262 /* Handle as files or dirs appropriately, but ignore . and .. */
2263 if (isDirectory
== expandDirs
&&
2264 (strcmpW(fd
.cFileName
, dotdotW
) != 0) &&
2265 (strcmpW(fd
.cFileName
, dotW
) != 0))
2267 thisCmdStart
= cmdStart
;
2268 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd
.cFileName
));
2271 strcpyW(fullitem
, dirsToWalk
->dirName
);
2272 strcatW(fullitem
, slashW
);
2273 strcatW(fullitem
, fd
.cFileName
);
2275 if (prefixlen
) lstrcpynW(fullitem
, item
, prefixlen
+ 1);
2276 fullitem
[prefixlen
] = 0x00;
2277 strcatW(fullitem
, fd
.cFileName
);
2281 /* Save away any existing for variable context (e.g. nested for loops)
2282 and restore it after executing the body of this for loop */
2284 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2285 forloopcontext
.variable
[varidx
] = fullitem
;
2287 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2288 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2290 cmdEnd
= thisCmdStart
;
2292 } while (FindNextFileW(hff
, &fd
) != 0);
2298 /* Save away any existing for variable context (e.g. nested for loops)
2299 and restore it after executing the body of this for loop */
2301 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2302 forloopcontext
.variable
[varidx
] = fullitem
;
2304 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2305 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2307 cmdEnd
= thisCmdStart
;
2310 } else if (useNumbers
) {
2311 /* Convert the first 3 numbers to signed longs and save */
2312 if (itemNum
<=3) numbers
[itemNum
-1] = atolW(item
);
2313 /* else ignore them! */
2315 /* Filesets - either a list of files, or a command to run and parse the output */
2316 } else if (doFileset
&& ((!forf_usebackq
&& *itemStart
!= '"') ||
2317 (forf_usebackq
&& *itemStart
!= '\''))) {
2322 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum
,
2323 wine_dbgstr_w(item
));
2325 /* If backquote or single quote, we need to launch that command
2326 and parse the results - use a temporary file */
2327 if ((forf_usebackq
&& *itemStart
== '`') ||
2328 (!forf_usebackq
&& *itemStart
== '\'')) {
2330 /* Use itemstart because the command is the whole set, not just the first token */
2331 itemparm
= itemStart
;
2334 /* Use item because the file to process is just the first item in the set */
2337 input
= WCMD_forf_getinputhandle(forf_usebackq
, itemparm
, (itemparm
==itemStart
));
2339 /* Process the input file */
2340 if (input
== INVALID_HANDLE_VALUE
) {
2341 WCMD_print_error ();
2342 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), item
);
2344 return; /* FOR loop aborts at first failure here */
2348 /* Read line by line until end of file */
2349 while (WCMD_fgets(buffer
, sizeof(buffer
)/sizeof(WCHAR
), input
)) {
2350 WCMD_parse_line(cmdStart
, firstCmd
, &cmdEnd
, variable
[1], buffer
, &doExecuted
,
2351 &forf_skip
, forf_eol
, forf_delims
, forf_tokens
);
2354 CloseHandle (input
);
2357 /* When we have processed the item as a whole command, abort future set processing */
2358 if (itemparm
==itemStart
) {
2363 /* Filesets - A string literal */
2364 } else if (doFileset
&& ((!forf_usebackq
&& *itemStart
== '"') ||
2365 (forf_usebackq
&& *itemStart
== '\''))) {
2367 /* Remove leading and trailing character, ready to parse with delims= delimiters
2368 Note that the last quote is removed from the set and the string terminates
2369 there to mimic windows */
2370 WCHAR
*strend
= strrchrW(itemStart
, forf_usebackq
?'\'':'"');
2376 /* Copy the item away from the global buffer used by WCMD_parameter */
2377 strcpyW(buffer
, itemStart
);
2378 WCMD_parse_line(cmdStart
, firstCmd
, &cmdEnd
, variable
[1], buffer
, &doExecuted
,
2379 &forf_skip
, forf_eol
, forf_delims
, forf_tokens
);
2381 /* Only one string can be supplied in the whole set, abort future set processing */
2386 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
2390 /* Move onto the next set line */
2391 if (thisSet
) thisSet
= thisSet
->nextcommand
;
2394 /* If /L is provided, now run the for loop */
2397 static const WCHAR fmt
[] = {'%','d','\0'};
2399 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2400 numbers
[0], numbers
[2], numbers
[1]);
2402 (numbers
[1]<0)? i
>=numbers
[2] : i
<=numbers
[2];
2405 sprintfW(thisNum
, fmt
, i
);
2406 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum
));
2408 thisCmdStart
= cmdStart
;
2411 /* Save away any existing for variable context (e.g. nested for loops)
2412 and restore it after executing the body of this for loop */
2414 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2415 forloopcontext
.variable
[varidx
] = thisNum
;
2417 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2418 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2420 cmdEnd
= thisCmdStart
;
2423 /* If we are walking directories, move on to any which remain */
2424 if (dirsToWalk
!= NULL
) {
2425 DIRECTORY_STACK
*nextDir
= dirsToWalk
->next
;
2426 heap_free(dirsToWalk
->dirName
);
2427 heap_free(dirsToWalk
);
2428 dirsToWalk
= nextDir
;
2429 if (dirsToWalk
) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2430 wine_dbgstr_w(dirsToWalk
->dirName
));
2431 else WINE_TRACE("Finished all directories.\n");
2434 } while (dirsToWalk
!= NULL
);
2436 /* Now skip over the do part if we did not perform the for loop so far.
2437 We store in cmdEnd the next command after the do block, but we only
2438 know this if something was run. If it has not been, we need to calculate
2441 thisCmdStart
= cmdStart
;
2442 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2443 WCMD_part_execute(&thisCmdStart
, firstCmd
, FALSE
, FALSE
);
2444 cmdEnd
= thisCmdStart
;
2447 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2448 all processing, OR it should be pointing to the end of && processing OR
2449 it should be pointing at the NULL end of bracket for the DO. The return
2450 value needs to be the NEXT command to execute, which it either is, or
2451 we need to step over the closing bracket */
2453 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
2456 /**************************************************************************
2459 * Simple on-line help. Help text is stored in the resource file.
2462 void WCMD_give_help (const WCHAR
*args
)
2466 args
= WCMD_skip_leading_spaces((WCHAR
*) args
);
2467 if (strlenW(args
) == 0) {
2468 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
2471 /* Display help message for builtin commands */
2472 for (i
=0; i
<=WCMD_EXIT
; i
++) {
2473 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2474 args
, -1, inbuilt
[i
], -1) == CSTR_EQUAL
) {
2475 WCMD_output_asis (WCMD_LoadMessage(i
));
2479 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2480 for (i
= 0; i
<= (sizeof(externals
)/sizeof(externals
[0])); i
++) {
2481 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2482 args
, -1, externals
[i
], -1) == CSTR_EQUAL
) {
2484 static const WCHAR helpW
[] = {' ', '/','?','\0'};
2486 strcatW(cmd
, helpW
);
2487 WCMD_run_program(cmd
, FALSE
);
2491 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), args
);
2496 /****************************************************************************
2499 * Batch file jump instruction. Not the most efficient algorithm ;-)
2500 * Prints error message if the specified label cannot be found - the file pointer is
2501 * then at EOF, effectively stopping the batch file.
2502 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2505 void WCMD_goto (CMD_LIST
**cmdList
) {
2507 WCHAR string
[MAX_PATH
];
2508 WCHAR
*labelend
= NULL
;
2509 const WCHAR labelEndsW
[] = {'>','<','|','&',' ',':','\t','\0'};
2511 /* Do not process any more parts of a processed multipart or multilines command */
2512 if (cmdList
) *cmdList
= NULL
;
2514 if (context
!= NULL
) {
2515 WCHAR
*paramStart
= param1
, *str
;
2516 static const WCHAR eofW
[] = {':','e','o','f','\0'};
2518 if (param1
[0] == 0x00) {
2519 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2523 /* Handle special :EOF label */
2524 if (lstrcmpiW (eofW
, param1
) == 0) {
2525 context
-> skip_rest
= TRUE
;
2529 /* Support goto :label as well as goto label plus remove trailing chars */
2530 if (*paramStart
== ':') paramStart
++;
2531 labelend
= strpbrkW(paramStart
, labelEndsW
);
2532 if (labelend
) *labelend
= 0x00;
2533 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart
));
2535 SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
2536 while (*paramStart
&&
2537 WCMD_fgets (string
, sizeof(string
)/sizeof(WCHAR
), context
-> h
)) {
2540 /* Ignore leading whitespace or no-echo character */
2541 while (*str
=='@' || isspaceW (*str
)) str
++;
2543 /* If the first real character is a : then this is a label */
2547 /* Skip spaces between : and label */
2548 while (isspaceW (*str
)) str
++;
2549 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str
));
2551 /* Label ends at whitespace or redirection characters */
2552 labelend
= strpbrkW(str
, labelEndsW
);
2553 if (labelend
) *labelend
= 0x00;
2554 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str
));
2556 if (lstrcmpiW (str
, paramStart
) == 0) return;
2559 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET
));
2560 context
-> skip_rest
= TRUE
;
2565 /*****************************************************************************
2568 * Push a directory onto the stack
2571 void WCMD_pushd (const WCHAR
*args
)
2573 struct env_stack
*curdir
;
2575 static const WCHAR parmD
[] = {'/','D','\0'};
2577 if (strchrW(args
, '/') != NULL
) {
2578 SetLastError(ERROR_INVALID_PARAMETER
);
2583 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
2584 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
2585 if( !curdir
|| !thisdir
) {
2588 WINE_ERR ("out of memory\n");
2592 /* Change directory using CD code with /D parameter */
2593 strcpyW(quals
, parmD
);
2594 GetCurrentDirectoryW (1024, thisdir
);
2596 WCMD_setshow_default(args
);
2602 curdir
-> next
= pushd_directories
;
2603 curdir
-> strings
= thisdir
;
2604 if (pushd_directories
== NULL
) {
2605 curdir
-> u
.stackdepth
= 1;
2607 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
2609 pushd_directories
= curdir
;
2614 /*****************************************************************************
2617 * Pop a directory from the stack
2620 void WCMD_popd (void) {
2621 struct env_stack
*temp
= pushd_directories
;
2623 if (!pushd_directories
)
2626 /* pop the old environment from the stack, and make it the current dir */
2627 pushd_directories
= temp
->next
;
2628 SetCurrentDirectoryW(temp
->strings
);
2629 LocalFree (temp
->strings
);
2633 /*******************************************************************
2634 * evaluate_if_comparison
2636 * Evaluates an "if" comparison operation
2639 * leftOperand [I] left operand, non NULL
2640 * operator [I] "if" binary comparison operator, non NULL
2641 * rightOperand [I] right operand, non NULL
2642 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2645 * Success: 1 if operator applied to the operands evaluates to TRUE
2646 * 0 if operator applied to the operands evaluates to FALSE
2647 * Failure: -1 if operator is not recognized
2649 static int evaluate_if_comparison(const WCHAR
*leftOperand
, const WCHAR
*operator,
2650 const WCHAR
*rightOperand
, int caseInsensitive
)
2652 WCHAR
*endptr_leftOp
, *endptr_rightOp
;
2653 long int leftOperand_int
, rightOperand_int
;
2655 static const WCHAR lssW
[] = {'l','s','s','\0'};
2656 static const WCHAR leqW
[] = {'l','e','q','\0'};
2657 static const WCHAR equW
[] = {'e','q','u','\0'};
2658 static const WCHAR neqW
[] = {'n','e','q','\0'};
2659 static const WCHAR geqW
[] = {'g','e','q','\0'};
2660 static const WCHAR gtrW
[] = {'g','t','r','\0'};
2662 /* == is a special case, as it always compares strings */
2663 if (!lstrcmpiW(operator, eqeqW
))
2664 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) == 0
2665 : lstrcmpW (leftOperand
, rightOperand
) == 0;
2667 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2668 leftOperand_int
= strtolW(leftOperand
, &endptr_leftOp
, 0);
2669 rightOperand_int
= strtolW(rightOperand
, &endptr_rightOp
, 0);
2670 int_operands
= (!*endptr_leftOp
) && (!*endptr_rightOp
);
2672 /* Perform actual (integer or string) comparison */
2673 if (!lstrcmpiW(operator, lssW
)) {
2675 return leftOperand_int
< rightOperand_int
;
2677 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) < 0
2678 : lstrcmpW (leftOperand
, rightOperand
) < 0;
2681 if (!lstrcmpiW(operator, leqW
)) {
2683 return leftOperand_int
<= rightOperand_int
;
2685 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) <= 0
2686 : lstrcmpW (leftOperand
, rightOperand
) <= 0;
2689 if (!lstrcmpiW(operator, equW
)) {
2691 return leftOperand_int
== rightOperand_int
;
2693 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) == 0
2694 : lstrcmpW (leftOperand
, rightOperand
) == 0;
2697 if (!lstrcmpiW(operator, neqW
)) {
2699 return leftOperand_int
!= rightOperand_int
;
2701 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) != 0
2702 : lstrcmpW (leftOperand
, rightOperand
) != 0;
2705 if (!lstrcmpiW(operator, geqW
)) {
2707 return leftOperand_int
>= rightOperand_int
;
2709 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) >= 0
2710 : lstrcmpW (leftOperand
, rightOperand
) >= 0;
2713 if (!lstrcmpiW(operator, gtrW
)) {
2715 return leftOperand_int
> rightOperand_int
;
2717 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) > 0
2718 : lstrcmpW (leftOperand
, rightOperand
) > 0;
2724 /****************************************************************************
2727 * Batch file conditional.
2729 * On entry, cmdlist will point to command containing the IF, and optionally
2730 * the first command to execute (if brackets not found)
2731 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2732 * If ('s were found, execute all within that bracket
2733 * Command may optionally be followed by an ELSE - need to skip instructions
2734 * in the else using the same logic
2736 * FIXME: Much more syntax checking needed!
2738 void WCMD_if (WCHAR
*p
, CMD_LIST
**cmdList
)
2740 int negate
; /* Negate condition */
2741 int test
; /* Condition evaluation result */
2742 WCHAR condition
[MAX_PATH
], *command
;
2743 static const WCHAR notW
[] = {'n','o','t','\0'};
2744 static const WCHAR errlvlW
[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2745 static const WCHAR existW
[] = {'e','x','i','s','t','\0'};
2746 static const WCHAR defdW
[] = {'d','e','f','i','n','e','d','\0'};
2747 static const WCHAR parmI
[] = {'/','I','\0'};
2748 int caseInsensitive
= (strstrW(quals
, parmI
) != NULL
);
2750 negate
= !lstrcmpiW(param1
,notW
);
2751 strcpyW(condition
, (negate
? param2
: param1
));
2752 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition
));
2754 if (!lstrcmpiW (condition
, errlvlW
)) {
2755 WCHAR
*param
= WCMD_parameter(p
, 1+negate
, NULL
, FALSE
, FALSE
);
2757 long int param_int
= strtolW(param
, &endptr
, 10);
2758 if (*endptr
) goto syntax_err
;
2759 test
= ((long int)errorlevel
>= param_int
);
2760 WCMD_parameter(p
, 2+negate
, &command
, FALSE
, FALSE
);
2762 else if (!lstrcmpiW (condition
, existW
)) {
2763 test
= (GetFileAttributesW(WCMD_parameter(p
, 1+negate
, NULL
, FALSE
, FALSE
))
2764 != INVALID_FILE_ATTRIBUTES
);
2765 WCMD_parameter(p
, 2+negate
, &command
, FALSE
, FALSE
);
2767 else if (!lstrcmpiW (condition
, defdW
)) {
2768 test
= (GetEnvironmentVariableW(WCMD_parameter(p
, 1+negate
, NULL
, FALSE
, FALSE
),
2770 WCMD_parameter(p
, 2+negate
, &command
, FALSE
, FALSE
);
2772 else { /* comparison operation */
2773 WCHAR leftOperand
[MAXSTRING
], rightOperand
[MAXSTRING
], operator[MAXSTRING
];
2776 strcpyW(leftOperand
, WCMD_parameter(p
, negate
+caseInsensitive
, ¶mStart
, TRUE
, FALSE
));
2780 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2781 p
= paramStart
+ strlenW(leftOperand
);
2782 while (*p
== ' ' || *p
== '\t')
2785 if (!strncmpW(p
, eqeqW
, strlenW(eqeqW
)))
2786 strcpyW(operator, eqeqW
);
2788 strcpyW(operator, WCMD_parameter(p
, 0, ¶mStart
, FALSE
, FALSE
));
2789 if (!*operator) goto syntax_err
;
2791 p
+= strlenW(operator);
2793 strcpyW(rightOperand
, WCMD_parameter(p
, 0, ¶mStart
, TRUE
, FALSE
));
2797 test
= evaluate_if_comparison(leftOperand
, operator, rightOperand
, caseInsensitive
);
2801 p
= paramStart
+ strlenW(rightOperand
);
2802 WCMD_parameter(p
, 0, &command
, FALSE
, FALSE
);
2805 /* Process rest of IF statement which is on the same line
2806 Note: This may process all or some of the cmdList (eg a GOTO) */
2807 WCMD_part_execute(cmdList
, command
, TRUE
, (test
!= negate
));
2811 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
2814 /****************************************************************************
2817 * Move a file, directory tree or wildcarded set of files.
2820 void WCMD_move (void)
2823 WIN32_FIND_DATAW fd
;
2825 WCHAR input
[MAX_PATH
];
2826 WCHAR output
[MAX_PATH
];
2828 WCHAR dir
[MAX_PATH
];
2829 WCHAR fname
[MAX_PATH
];
2830 WCHAR ext
[MAX_PATH
];
2832 if (param1
[0] == 0x00) {
2833 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2837 /* If no destination supplied, assume current directory */
2838 if (param2
[0] == 0x00) {
2839 strcpyW(param2
, dotW
);
2842 /* If 2nd parm is directory, then use original filename */
2843 /* Convert partial path to full path */
2844 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
2845 GetFullPathNameW(param2
, sizeof(output
)/sizeof(WCHAR
), output
, NULL
);
2846 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
2847 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
2849 /* Split into components */
2850 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
2852 hff
= FindFirstFileW(input
, &fd
);
2853 if (hff
== INVALID_HANDLE_VALUE
)
2857 WCHAR dest
[MAX_PATH
];
2858 WCHAR src
[MAX_PATH
];
2862 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
2864 /* Build src & dest name */
2865 strcpyW(src
, drive
);
2868 /* See if dest is an existing directory */
2869 attribs
= GetFileAttributesW(output
);
2870 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
2871 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
2872 strcpyW(dest
, output
);
2873 strcatW(dest
, slashW
);
2874 strcatW(dest
, fd
.cFileName
);
2876 strcpyW(dest
, output
);
2879 strcatW(src
, fd
.cFileName
);
2881 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
2882 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
2884 /* If destination exists, prompt unless /Y supplied */
2885 if (GetFileAttributesW(dest
) != INVALID_FILE_ATTRIBUTES
) {
2887 WCHAR copycmd
[MAXSTRING
];
2890 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2891 if (strstrW (quals
, parmNoY
))
2893 else if (strstrW (quals
, parmY
))
2896 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
2897 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, sizeof(copycmd
)/sizeof(WCHAR
));
2898 force
= (len
&& len
< (sizeof(copycmd
)/sizeof(WCHAR
))
2899 && ! lstrcmpiW (copycmd
, parmY
));
2902 /* Prompt if overwriting */
2906 /* Ask for confirmation */
2907 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
2908 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
2909 LocalFree(question
);
2911 /* So delete the destination prior to the move */
2913 if (!DeleteFileW(dest
)) {
2914 WCMD_print_error ();
2923 status
= MoveFileW(src
, dest
);
2925 status
= 1; /* Anything other than 0 to prevent error msg below */
2929 WCMD_print_error ();
2932 } while (FindNextFileW(hff
, &fd
) != 0);
2937 /****************************************************************************
2940 * Suspend execution of a batch script until a key is typed
2943 void WCMD_pause (void)
2949 HANDLE hIn
= GetStdHandle(STD_INPUT_HANDLE
);
2951 have_console
= GetConsoleMode(hIn
, &oldmode
);
2953 SetConsoleMode(hIn
, 0);
2955 WCMD_output_asis(anykey
);
2956 WCMD_ReadFile(hIn
, &key
, 1, &count
);
2958 SetConsoleMode(hIn
, oldmode
);
2961 /****************************************************************************
2964 * Delete a directory.
2967 void WCMD_remove_dir (WCHAR
*args
) {
2970 int argsProcessed
= 0;
2972 static const WCHAR parmS
[] = {'/','S','\0'};
2973 static const WCHAR parmQ
[] = {'/','Q','\0'};
2975 /* Loop through all args */
2977 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
2978 if (argN
&& argN
[0] != '/') {
2979 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
2980 wine_dbgstr_w(quals
));
2983 /* If subdirectory search not supplied, just try to remove
2984 and report error if it fails (eg if it contains a file) */
2985 if (strstrW (quals
, parmS
) == NULL
) {
2986 if (!RemoveDirectoryW(thisArg
)) WCMD_print_error ();
2988 /* Otherwise use ShFileOp to recursively remove a directory */
2991 SHFILEOPSTRUCTW lpDir
;
2994 if (strstrW (quals
, parmQ
) == NULL
) {
2996 WCHAR question
[MAXSTRING
];
2997 static const WCHAR fmt
[] = {'%','s',' ','\0'};
2999 /* Ask for confirmation */
3000 wsprintfW(question
, fmt
, thisArg
);
3001 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
3003 /* Abort if answer is 'N' */
3010 lpDir
.pFrom
= thisArg
;
3011 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
3012 lpDir
.wFunc
= FO_DELETE
;
3014 /* SHFileOperationW needs file list with a double null termination */
3015 thisArg
[lstrlenW(thisArg
) + 1] = 0x00;
3017 if (SHFileOperationW(&lpDir
)) WCMD_print_error ();
3022 /* Handle no valid args */
3023 if (argsProcessed
== 0) {
3024 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
3030 /****************************************************************************
3036 void WCMD_rename (void)
3040 WIN32_FIND_DATAW fd
;
3041 WCHAR input
[MAX_PATH
];
3042 WCHAR
*dotDst
= NULL
;
3044 WCHAR dir
[MAX_PATH
];
3045 WCHAR fname
[MAX_PATH
];
3046 WCHAR ext
[MAX_PATH
];
3050 /* Must be at least two args */
3051 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
3052 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
3057 /* Destination cannot contain a drive letter or directory separator */
3058 if ((strchrW(param2
,':') != NULL
) || (strchrW(param2
,'\\') != NULL
)) {
3059 SetLastError(ERROR_INVALID_PARAMETER
);
3065 /* Convert partial path to full path */
3066 GetFullPathNameW(param1
, sizeof(input
)/sizeof(WCHAR
), input
, NULL
);
3067 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
3068 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
3069 dotDst
= strchrW(param2
, '.');
3071 /* Split into components */
3072 WCMD_splitpath(input
, drive
, dir
, fname
, ext
);
3074 hff
= FindFirstFileW(input
, &fd
);
3075 if (hff
== INVALID_HANDLE_VALUE
)
3079 WCHAR dest
[MAX_PATH
];
3080 WCHAR src
[MAX_PATH
];
3081 WCHAR
*dotSrc
= NULL
;
3084 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
3086 /* FIXME: If dest name or extension is *, replace with filename/ext
3087 part otherwise use supplied name. This supports:
3089 ren jim.* fred.* etc
3090 However, windows has a more complex algorithm supporting eg
3091 ?'s and *'s mid name */
3092 dotSrc
= strchrW(fd
.cFileName
, '.');
3094 /* Build src & dest name */
3095 strcpyW(src
, drive
);
3098 dirLen
= strlenW(src
);
3099 strcatW(src
, fd
.cFileName
);
3102 if (param2
[0] == '*') {
3103 strcatW(dest
, fd
.cFileName
);
3104 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
3106 strcatW(dest
, param2
);
3107 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
3110 /* Build Extension */
3111 if (dotDst
&& (*(dotDst
+1)=='*')) {
3112 if (dotSrc
) strcatW(dest
, dotSrc
);
3113 } else if (dotDst
) {
3114 if (dotDst
) strcatW(dest
, dotDst
);
3117 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
3118 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
3120 status
= MoveFileW(src
, dest
);
3123 WCMD_print_error ();
3126 } while (FindNextFileW(hff
, &fd
) != 0);
3131 /*****************************************************************************
3134 * Make a copy of the environment.
3136 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
3146 len
+= (strlenW(&env
[len
]) + 1);
3148 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
3151 WINE_ERR("out of memory\n");
3154 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
3160 /*****************************************************************************
3163 * setlocal pushes the environment onto a stack
3164 * Save the environment as unicode so we don't screw anything up.
3166 void WCMD_setlocal (const WCHAR
*s
) {
3168 struct env_stack
*env_copy
;
3169 WCHAR cwd
[MAX_PATH
];
3171 static const WCHAR ondelayW
[] = {'E','N','A','B','L','E','D','E','L','A',
3172 'Y','E','D','E','X','P','A','N','S','I',
3174 static const WCHAR offdelayW
[] = {'D','I','S','A','B','L','E','D','E','L',
3175 'A','Y','E','D','E','X','P','A','N','S',
3178 /* setlocal does nothing outside of batch programs */
3179 if (!context
) return;
3181 /* DISABLEEXTENSIONS ignored */
3183 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3184 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3185 if (!strcmpiW(param1
, ondelayW
) || !strcmpiW(param2
, ondelayW
)) {
3187 } else if (!strcmpiW(param1
, offdelayW
) || !strcmpiW(param2
, offdelayW
)) {
3190 newdelay
= delayedsubst
;
3192 WINE_TRACE("Setting delayed expansion to %d\n", newdelay
);
3194 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
3197 WINE_ERR ("out of memory\n");
3201 env
= GetEnvironmentStringsW ();
3202 env_copy
->strings
= WCMD_dupenv (env
);
3203 if (env_copy
->strings
)
3205 env_copy
->batchhandle
= context
->h
;
3206 env_copy
->next
= saved_environment
;
3207 env_copy
->delayedsubst
= delayedsubst
;
3208 delayedsubst
= newdelay
;
3209 saved_environment
= env_copy
;
3211 /* Save the current drive letter */
3212 GetCurrentDirectoryW(MAX_PATH
, cwd
);
3213 env_copy
->u
.cwd
= cwd
[0];
3216 LocalFree (env_copy
);
3218 FreeEnvironmentStringsW (env
);
3222 /*****************************************************************************
3225 * endlocal pops the environment off a stack
3226 * Note: When searching for '=', search from WCHAR position 1, to handle
3227 * special internal environment variables =C:, =D: etc
3229 void WCMD_endlocal (void) {
3230 WCHAR
*env
, *old
, *p
;
3231 struct env_stack
*temp
;
3234 /* setlocal does nothing outside of batch programs */
3235 if (!context
) return;
3237 /* setlocal needs a saved environment from within the same context (batch
3238 program) as it was saved in */
3239 if (!saved_environment
|| saved_environment
->batchhandle
!= context
->h
)
3242 /* pop the old environment from the stack */
3243 temp
= saved_environment
;
3244 saved_environment
= temp
->next
;
3246 /* delete the current environment, totally */
3247 env
= GetEnvironmentStringsW ();
3248 old
= WCMD_dupenv (env
);
3251 n
= strlenW(&old
[len
]) + 1;
3252 p
= strchrW(&old
[len
] + 1, '=');
3256 SetEnvironmentVariableW (&old
[len
], NULL
);
3261 FreeEnvironmentStringsW (env
);
3263 /* restore old environment */
3264 env
= temp
->strings
;
3266 delayedsubst
= temp
->delayedsubst
;
3267 WINE_TRACE("Delayed expansion now %d\n", delayedsubst
);
3269 n
= strlenW(&env
[len
]) + 1;
3270 p
= strchrW(&env
[len
] + 1, '=');
3274 SetEnvironmentVariableW (&env
[len
], p
);
3279 /* Restore current drive letter */
3280 if (IsCharAlphaW(temp
->u
.cwd
)) {
3282 WCHAR cwd
[MAX_PATH
];
3283 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
3285 wsprintfW(envvar
, fmt
, temp
->u
.cwd
);
3286 if (GetEnvironmentVariableW(envvar
, cwd
, MAX_PATH
)) {
3287 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
3288 SetCurrentDirectoryW(cwd
);
3296 /*****************************************************************************
3297 * WCMD_setshow_default
3299 * Set/Show the current default directory
3302 void WCMD_setshow_default (const WCHAR
*args
) {
3308 WIN32_FIND_DATAW fd
;
3310 static const WCHAR parmD
[] = {'/','D','\0'};
3312 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args
));
3314 /* Skip /D and trailing whitespace if on the front of the command line */
3315 if (CompareStringW(LOCALE_USER_DEFAULT
,
3316 NORM_IGNORECASE
| SORT_STRINGSORT
,
3317 args
, 2, parmD
, -1) == CSTR_EQUAL
) {
3319 while (*args
&& (*args
==' ' || *args
=='\t'))
3323 GetCurrentDirectoryW(sizeof(cwd
)/sizeof(WCHAR
), cwd
);
3324 if (strlenW(args
) == 0) {
3325 strcatW (cwd
, newlineW
);
3326 WCMD_output_asis (cwd
);
3329 /* Remove any double quotes, which may be in the
3330 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3333 if (*args
!= '"') *pos
++ = *args
;
3336 while (pos
> string
&& (*(pos
-1) == ' ' || *(pos
-1) == '\t'))
3340 /* Search for appropriate directory */
3341 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
3342 hff
= FindFirstFileW(string
, &fd
);
3343 if (hff
!= INVALID_HANDLE_VALUE
) {
3345 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
3346 WCHAR fpath
[MAX_PATH
];
3348 WCHAR dir
[MAX_PATH
];
3349 WCHAR fname
[MAX_PATH
];
3350 WCHAR ext
[MAX_PATH
];
3351 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
3353 /* Convert path into actual directory spec */
3354 GetFullPathNameW(string
, sizeof(fpath
)/sizeof(WCHAR
), fpath
, NULL
);
3355 WCMD_splitpath(fpath
, drive
, dir
, fname
, ext
);
3358 wsprintfW(string
, fmt
, drive
, dir
, fd
.cFileName
);
3361 } while (FindNextFileW(hff
, &fd
) != 0);
3365 /* Change to that directory */
3366 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
3368 status
= SetCurrentDirectoryW(string
);
3371 WCMD_print_error ();
3375 /* Save away the actual new directory, to store as current location */
3376 GetCurrentDirectoryW (sizeof(string
)/sizeof(WCHAR
), string
);
3378 /* Restore old directory if drive letter would change, and
3379 CD x:\directory /D (or pushd c:\directory) not supplied */
3380 if ((strstrW(quals
, parmD
) == NULL
) &&
3381 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
3382 SetCurrentDirectoryW(cwd
);
3386 /* Set special =C: type environment variable, for drive letter of
3387 change of directory, even if path was restored due to missing
3388 /D (allows changing drive letter when not resident on that
3390 if ((string
[1] == ':') && IsCharAlphaW(string
[0])) {
3392 strcpyW(env
, equalW
);
3393 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
3395 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
3396 SetEnvironmentVariableW(env
, string
);
3403 /****************************************************************************
3406 * Set/Show the system date
3407 * FIXME: Can't change date yet
3410 void WCMD_setshow_date (void) {
3412 WCHAR curdate
[64], buffer
[64];
3414 static const WCHAR parmT
[] = {'/','T','\0'};
3416 if (strlenW(param1
) == 0) {
3417 if (GetDateFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
,
3418 curdate
, sizeof(curdate
)/sizeof(WCHAR
))) {
3419 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
3420 if (strstrW (quals
, parmT
) == NULL
) {
3421 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
3422 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
3424 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
3428 else WCMD_print_error ();
3431 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
3435 /****************************************************************************
3437 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3440 static int WCMD_compare( const void *a
, const void *b
)
3443 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
3444 r
= CompareStringW( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
3445 *str_a
, strcspnW(*str_a
, equalW
), *str_b
, strcspnW(*str_b
, equalW
) );
3446 if( r
== CSTR_LESS_THAN
) return -1;
3447 if( r
== CSTR_GREATER_THAN
) return 1;
3451 /****************************************************************************
3452 * WCMD_setshow_sortenv
3454 * sort variables into order for display
3455 * Optionally only display those who start with a stub
3456 * returns the count displayed
3458 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
3460 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
3463 if (stub
) stublen
= strlenW(stub
);
3465 /* count the number of strings, and the total length */
3467 len
+= (strlenW(&s
[len
]) + 1);
3471 /* add the strings to an array */
3472 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
3476 for( i
=1; i
<count
; i
++ )
3477 str
[i
] = str
[i
-1] + strlenW(str
[i
-1]) + 1;
3479 /* sort the array */
3480 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
3483 for( i
=0; i
<count
; i
++ ) {
3484 if (!stub
|| CompareStringW(LOCALE_USER_DEFAULT
,
3485 NORM_IGNORECASE
| SORT_STRINGSORT
,
3486 str
[i
], stublen
, stub
, -1) == CSTR_EQUAL
) {
3487 /* Don't display special internal variables */
3488 if (str
[i
][0] != '=') {
3489 WCMD_output_asis(str
[i
]);
3490 WCMD_output_asis(newlineW
);
3497 return displayedcount
;
3500 /****************************************************************************
3501 * WCMD_getprecedence
3502 * Return the precedence of a particular operator
3504 static int WCMD_getprecedence(const WCHAR in
)
3545 /****************************************************************************
3547 * Push either a number or name (environment variable) onto the supplied
3550 static void WCMD_pushnumber(WCHAR
*var
, int num
, VARSTACK
**varstack
) {
3551 VARSTACK
*thisstack
= heap_alloc(sizeof(VARSTACK
));
3552 thisstack
->isnum
= (var
== NULL
);
3554 thisstack
->variable
= var
;
3555 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var
));
3557 thisstack
->value
= num
;
3558 WINE_TRACE("Pushed number %d\n", num
);
3560 thisstack
->next
= *varstack
;
3561 *varstack
= thisstack
;
3564 /****************************************************************************
3566 * Returns the value of the top number or environment variable on the stack
3567 * and leaves the item on the stack.
3569 static int WCMD_peeknumber(VARSTACK
**varstack
) {
3574 thisvar
= *varstack
;
3575 if (!thisvar
->isnum
) {
3576 WCHAR tmpstr
[MAXSTRING
];
3577 if (GetEnvironmentVariableW(thisvar
->variable
, tmpstr
, MAXSTRING
)) {
3578 result
= strtoulW(tmpstr
,NULL
,0);
3580 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar
->variable
), result
);
3582 result
= thisvar
->value
;
3585 WINE_TRACE("Peeked number %d\n", result
);
3589 /****************************************************************************
3591 * Returns the value of the top number or environment variable on the stack
3592 * and removes the item from the stack.
3594 static int WCMD_popnumber(VARSTACK
**varstack
) {
3599 thisvar
= *varstack
;
3600 result
= WCMD_peeknumber(varstack
);
3601 if (!thisvar
->isnum
) heap_free(thisvar
->variable
);
3602 *varstack
= thisvar
->next
;
3605 WINE_TRACE("Popped number %d\n", result
);
3609 /****************************************************************************
3611 * Push an operator onto the supplied stack
3613 static void WCMD_pushoperator(WCHAR op
, int precedence
, OPSTACK
**opstack
) {
3614 OPSTACK
*thisstack
= heap_alloc(sizeof(OPSTACK
));
3615 thisstack
->precedence
= precedence
;
3617 thisstack
->next
= *opstack
;
3618 WINE_TRACE("Pushed operator %c\n", op
);
3619 *opstack
= thisstack
;
3622 /****************************************************************************
3624 * Returns the operator from the top of the stack and removes the item from
3627 static WCHAR
WCMD_popoperator(OPSTACK
**opstack
) {
3633 result
= thisop
->op
;
3634 *opstack
= thisop
->next
;
3637 WINE_TRACE("Popped operator %c\n", result
);
3641 /****************************************************************************
3643 * Actions the top operator on the stack against the first and sometimes
3644 * second value on the variable stack, and pushes the result
3645 * Returns non-zero on error.
3647 static int WCMD_reduce(OPSTACK
**opstack
, VARSTACK
**varstack
) {
3652 if (!*opstack
|| !*varstack
) {
3653 WINE_TRACE("No operators for the reduce\n");
3654 return WCMD_NOOPERATOR
;
3657 /* Remove the top operator */
3659 *opstack
= (*opstack
)->next
;
3660 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop
->op
);
3662 /* One variable operators */
3663 var1
= WCMD_popnumber(varstack
);
3664 switch (thisop
->op
) {
3665 case '!': WCMD_pushnumber(NULL
, !var1
, varstack
);
3667 case '~': WCMD_pushnumber(NULL
, ~var1
, varstack
);
3669 case OP_POSITIVE
: WCMD_pushnumber(NULL
, var1
, varstack
);
3671 case OP_NEGATIVE
: WCMD_pushnumber(NULL
, -var1
, varstack
);
3675 /* Two variable operators */
3677 WINE_TRACE("No operands left for the reduce?\n");
3678 return WCMD_NOOPERAND
;
3680 switch (thisop
->op
) {
3685 break; /* Handled above */
3686 case '*': var2
= WCMD_popnumber(varstack
);
3687 WCMD_pushnumber(NULL
, var2
*var1
, varstack
);
3689 case '/': var2
= WCMD_popnumber(varstack
);
3690 if (var1
== 0) return WCMD_DIVIDEBYZERO
;
3691 WCMD_pushnumber(NULL
, var2
/var1
, varstack
);
3693 case '+': var2
= WCMD_popnumber(varstack
);
3694 WCMD_pushnumber(NULL
, var2
+var1
, varstack
);
3696 case '-': var2
= WCMD_popnumber(varstack
);
3697 WCMD_pushnumber(NULL
, var2
-var1
, varstack
);
3699 case '&': var2
= WCMD_popnumber(varstack
);
3700 WCMD_pushnumber(NULL
, var2
&var1
, varstack
);
3702 case '%': var2
= WCMD_popnumber(varstack
);
3703 if (var1
== 0) return WCMD_DIVIDEBYZERO
;
3704 WCMD_pushnumber(NULL
, var2
%var1
, varstack
);
3706 case '^': var2
= WCMD_popnumber(varstack
);
3707 WCMD_pushnumber(NULL
, var2
^var1
, varstack
);
3709 case '<': var2
= WCMD_popnumber(varstack
);
3710 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3711 which differs from the compiler (for example gcc) so being explicit. */
3712 if (var1
< 0 || var1
>= (8 * sizeof(INT
))) {
3713 WCMD_pushnumber(NULL
, 0, varstack
);
3715 WCMD_pushnumber(NULL
, var2
<<var1
, varstack
);
3718 case '>': var2
= WCMD_popnumber(varstack
);
3719 WCMD_pushnumber(NULL
, var2
>>var1
, varstack
);
3721 case '|': var2
= WCMD_popnumber(varstack
);
3722 WCMD_pushnumber(NULL
, var2
|var1
, varstack
);
3738 /* The left of an equals must be one variable */
3739 if (!(*varstack
) || (*varstack
)->isnum
) {
3740 return WCMD_NOOPERAND
;
3743 /* Make the number stack grow by inserting the value of the variable */
3744 var2
= WCMD_peeknumber(varstack
);
3745 WCMD_pushnumber(NULL
, var2
, varstack
);
3746 WCMD_pushnumber(NULL
, var1
, varstack
);
3748 /* Make the operand stack grow by pushing the assign operator plus the
3749 operator to perform */
3750 while (calcassignments
[i
].op
!= ' ' &&
3751 calcassignments
[i
].calculatedop
!= thisop
->op
) {
3754 if (calcassignments
[i
].calculatedop
== ' ') {
3755 WINE_ERR("Unexpected operator %c\n", thisop
->op
);
3756 return WCMD_NOOPERATOR
;
3758 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack
);
3759 WCMD_pushoperator(calcassignments
[i
].op
,
3760 WCMD_getprecedence(calcassignments
[i
].op
), opstack
);
3766 WCHAR intFormat
[] = {'%','d','\0'};
3767 WCHAR result
[MAXSTRING
];
3769 /* Build the result, then push it onto the stack */
3770 sprintfW(result
, intFormat
, var1
);
3771 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack
)->variable
),
3772 wine_dbgstr_w(result
));
3773 SetEnvironmentVariableW((*varstack
)->variable
, result
);
3774 var2
= WCMD_popnumber(varstack
);
3775 WCMD_pushnumber(NULL
, var1
, varstack
);
3779 default: WINE_ERR("Unrecognized operator %c\n", thisop
->op
);
3787 /****************************************************************************
3788 * WCMD_handleExpression
3789 * Handles an expression provided to set /a - If it finds brackets, it uses
3790 * recursion to process the parts in brackets.
3792 static int WCMD_handleExpression(WCHAR
**expr
, int *ret
, int depth
)
3794 static const WCHAR mathDelims
[] = {' ','\t','(',')','!','~','-','*','/','%',
3795 '+','<','>','&','^','|','=',',','\0' };
3798 BOOL lastwasnumber
= FALSE
; /* FALSE makes a minus at the start of the expression easier to handle */
3799 OPSTACK
*opstackhead
= NULL
;
3800 VARSTACK
*varstackhead
= NULL
;
3801 WCHAR foundhalf
= 0;
3804 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr
));
3807 /* Iterate through until whole expression is processed */
3808 while (pos
&& *pos
) {
3811 /* Skip whitespace to get to the next character to process*/
3812 while (*pos
&& (*pos
==' ' || *pos
=='\t')) pos
++;
3813 if (!*pos
) goto exprreturn
;
3815 /* If we have found anything other than an operator then its a number/variable */
3816 if (strchrW(mathDelims
, *pos
) == NULL
) {
3817 WCHAR
*parmstart
, *parm
, *dupparm
;
3820 /* Cannot have an expression with var/number twice, without an operator
3821 in-between, nor or number following a half constructed << or >> operator */
3822 if (lastwasnumber
|| foundhalf
) {
3823 rc
= WCMD_NOOPERATOR
;
3824 goto exprerrorreturn
;
3826 lastwasnumber
= TRUE
;
3828 if (isdigitW(*pos
)) {
3829 /* For a number - just push it onto the stack */
3830 int num
= strtoulW(pos
, &nextpos
, 0);
3831 WCMD_pushnumber(NULL
, num
, &varstackhead
);
3834 /* Verify the number was validly formed */
3835 if (*nextpos
&& (strchrW(mathDelims
, *nextpos
) == NULL
)) {
3836 rc
= WCMD_BADHEXOCT
;
3837 goto exprerrorreturn
;
3841 /* For a variable - just push it onto the stack */
3842 parm
= WCMD_parameter_with_delims(pos
, 0, &parmstart
, FALSE
, FALSE
, mathDelims
);
3843 dupparm
= heap_strdupW(parm
);
3844 WCMD_pushnumber(dupparm
, 0, &varstackhead
);
3845 pos
= parmstart
+ strlenW(dupparm
);
3850 /* We have found an operator. Some operators are one character, some two, and the minus
3851 and plus signs need special processing as they can be either operators or just influence
3852 the parameter which follows them */
3853 if (foundhalf
&& (*pos
!= foundhalf
)) {
3854 /* Badly constructed operator pair */
3855 rc
= WCMD_NOOPERATOR
;
3856 goto exprerrorreturn
;
3859 treatasnumber
= FALSE
; /* We are processing an operand */
3862 /* > and < are special as they are double character operators (and spaces can be between them!)
3863 If we see these for the first time, set a flag, and second time around we continue.
3864 Note these double character operators are stored as just one of the characters on the stack */
3866 case '<': if (!foundhalf
) {
3871 /* We have found the rest, so clear up the knowledge of the half completed part and
3872 drop through to normal operator processing */
3876 case '=': if (*pos
=='=') {
3877 /* = is special cased as if the last was an operator then we may have e.g. += or
3878 *= etc which we need to handle by replacing the operator that is on the stack
3879 with a calculated assignment equivalent */
3880 if (!lastwasnumber
&& opstackhead
) {
3882 while (calcassignments
[i
].op
!= ' ' && calcassignments
[i
].op
!= opstackhead
->op
) {
3885 if (calcassignments
[i
].op
== ' ') {
3886 rc
= WCMD_NOOPERAND
;
3887 goto exprerrorreturn
;
3889 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3890 when the general operator handling happens further down. */
3891 *pos
= calcassignments
[i
].calculatedop
;
3892 WCMD_popoperator(&opstackhead
);
3898 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3899 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3900 case '+': if (!lastwasnumber
&& *pos
=='+') *pos
= OP_POSITIVE
;
3902 case '-': if (!lastwasnumber
&& *pos
=='-') *pos
= OP_NEGATIVE
;
3905 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3906 case '!': /* drop through */
3907 case '~': /* drop through */
3908 case '/': /* drop through */
3909 case '%': /* drop through */
3910 case '&': /* drop through */
3911 case '^': /* drop through */
3912 case '*': /* drop through */
3914 /* General code for handling most of the operators - look at the
3915 precedence of the top item on the stack, and see if we need to
3916 action the stack before we push something else onto it. */
3918 int precedence
= WCMD_getprecedence(*pos
);
3919 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos
,
3920 precedence
, !opstackhead
?-1:opstackhead
->precedence
);
3922 /* In general, for things with the same precedence, reduce immediately
3923 except for assignments and unary operators which do not */
3924 while (!rc
&& opstackhead
&&
3925 ((opstackhead
->precedence
> precedence
) ||
3926 ((opstackhead
->precedence
== precedence
) &&
3927 (precedence
!= 1) && (precedence
!= 8)))) {
3928 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
3930 if (rc
) goto exprerrorreturn
;
3931 WCMD_pushoperator(*pos
, precedence
, &opstackhead
);
3936 /* comma means start a new expression, ie calculate what we have */
3939 int prevresult
= -1;
3940 WINE_TRACE("Found expression delimiter - reducing exising stacks\n");
3941 while (!rc
&& opstackhead
) {
3942 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
3944 if (rc
) goto exprerrorreturn
;
3945 /* If we have anything other than one number left, error
3946 otherwise throw the number away */
3947 if (!varstackhead
|| varstackhead
->next
) {
3948 rc
= WCMD_NOOPERATOR
;
3949 goto exprerrorreturn
;
3951 prevresult
= WCMD_popnumber(&varstackhead
);
3952 WINE_TRACE("Expression resolved to %d\n", prevresult
);
3953 heap_free(varstackhead
);
3954 varstackhead
= NULL
;
3959 /* Open bracket - use iteration to parse the inner expression, then continue */
3963 rc
= WCMD_handleExpression(&pos
, &exprresult
, depth
+1);
3964 if (rc
) goto exprerrorreturn
;
3965 WCMD_pushnumber(NULL
, exprresult
, &varstackhead
);
3969 /* Close bracket - we have finished this depth, calculate and return */
3972 treatasnumber
= TRUE
; /* Things in brackets result in a number */
3975 goto exprerrorreturn
;
3981 WINE_ERR("Unrecognized operator %c\n", *pos
);
3984 lastwasnumber
= treatasnumber
;
3990 /* We need to reduce until we have a single number (or variable) on the
3991 stack and set the return value to that */
3992 while (!rc
&& opstackhead
) {
3993 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
3995 if (rc
) goto exprerrorreturn
;
3997 /* If we have anything other than one number left, error
3998 otherwise throw the number away */
3999 if (!varstackhead
|| varstackhead
->next
) {
4000 rc
= WCMD_NOOPERATOR
;
4001 goto exprerrorreturn
;
4004 /* Now get the number (and convert if its just a variable name) */
4005 *ret
= WCMD_popnumber(&varstackhead
);
4008 /* Free all remaining memory */
4009 while (opstackhead
) WCMD_popoperator(&opstackhead
);
4010 while (varstackhead
) WCMD_popnumber(&varstackhead
);
4012 WINE_TRACE("Returning result %d, rc %d\n", *ret
, rc
);
4016 /****************************************************************************
4019 * Set/Show the environment variables
4022 void WCMD_setshow_env (WCHAR
*s
) {
4027 static const WCHAR parmP
[] = {'/','P','\0'};
4028 static const WCHAR parmA
[] = {'/','A','\0'};
4029 WCHAR string
[MAXSTRING
];
4031 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
4032 env
= GetEnvironmentStringsW();
4033 WCMD_setshow_sortenv( env
, NULL
);
4037 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4038 if (CompareStringW(LOCALE_USER_DEFAULT
,
4039 NORM_IGNORECASE
| SORT_STRINGSORT
,
4040 s
, 2, parmP
, -1) == CSTR_EQUAL
) {
4044 while (*s
&& (*s
==' ' || *s
=='\t')) s
++;
4045 /* set /P "var=value"jim ignores anything after the last quote */
4048 lastquote
= WCMD_strip_quotes(s
);
4049 if (lastquote
) *lastquote
= 0x00;
4050 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s
));
4053 /* If no parameter, or no '=' sign, return an error */
4054 if (!(*s
) || ((p
= strchrW (s
, '=')) == NULL
)) {
4055 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
4059 /* Output the prompt */
4061 if (strlenW(p
) != 0) WCMD_output_asis(p
);
4063 /* Read the reply */
4064 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
)/sizeof(WCHAR
), &count
);
4066 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
4067 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
4068 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
4069 wine_dbgstr_w(string
));
4070 status
= SetEnvironmentVariableW(s
, string
);
4073 /* See if /A supplied, and if so calculate the results of all the expressions */
4074 } else if (CompareStringW(LOCALE_USER_DEFAULT
,
4075 NORM_IGNORECASE
| SORT_STRINGSORT
,
4076 s
, 2, parmA
, -1) == CSTR_EQUAL
) {
4077 /* /A supplied, so evaluate expressions and set variables appropriately */
4078 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4079 /* of the final computation */
4085 /* Remove all quotes before doing any calculations */
4086 thisexpr
= heap_alloc((strlenW(s
+2)+1) * sizeof(WCHAR
));
4090 if (*src
!= '"') *dst
++ = *src
;
4095 /* Now calculate the results of the expression */
4097 rc
= WCMD_handleExpression(&src
, &result
, 0);
4098 heap_free(thisexpr
);
4100 /* If parsing failed, issue the error message */
4102 WCMD_output_stderr(WCMD_LoadMessage(rc
));
4106 /* If we have no context (interactive or cmd.exe /c) print the final result */
4108 static const WCHAR fmt
[] = {'%','d','\0'};
4109 sprintfW(string
, fmt
, result
);
4110 WCMD_output_asis(string
);
4116 /* set "var=value"jim ignores anything after the last quote */
4119 lastquote
= WCMD_strip_quotes(s
);
4120 if (lastquote
) *lastquote
= 0x00;
4121 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s
));
4124 p
= strchrW (s
, '=');
4126 env
= GetEnvironmentStringsW();
4127 if (WCMD_setshow_sortenv( env
, s
) == 0) {
4128 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
4135 if (strlenW(p
) == 0) p
= NULL
;
4136 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
4138 status
= SetEnvironmentVariableW(s
, p
);
4139 gle
= GetLastError();
4140 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
4142 } else if ((!status
)) WCMD_print_error();
4143 else errorlevel
= 0;
4147 /****************************************************************************
4150 * Set/Show the path environment variable
4153 void WCMD_setshow_path (const WCHAR
*args
) {
4157 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
4158 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
4160 if (strlenW(param1
) == 0 && strlenW(param2
) == 0) {
4161 status
= GetEnvironmentVariableW(pathW
, string
, sizeof(string
)/sizeof(WCHAR
));
4163 WCMD_output_asis ( pathEqW
);
4164 WCMD_output_asis ( string
);
4165 WCMD_output_asis ( newlineW
);
4168 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH
));
4172 if (*args
== '=') args
++; /* Skip leading '=' */
4173 status
= SetEnvironmentVariableW(pathW
, args
);
4174 if (!status
) WCMD_print_error();
4178 /****************************************************************************
4179 * WCMD_setshow_prompt
4181 * Set or show the command prompt.
4184 void WCMD_setshow_prompt (void) {
4187 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
4189 if (strlenW(param1
) == 0) {
4190 SetEnvironmentVariableW(promptW
, NULL
);
4194 while ((*s
== '=') || (*s
== ' ') || (*s
== '\t')) s
++;
4195 if (strlenW(s
) == 0) {
4196 SetEnvironmentVariableW(promptW
, NULL
);
4198 else SetEnvironmentVariableW(promptW
, s
);
4202 /****************************************************************************
4205 * Set/Show the system time
4206 * FIXME: Can't change time yet
4209 void WCMD_setshow_time (void) {
4211 WCHAR curtime
[64], buffer
[64];
4214 static const WCHAR parmT
[] = {'/','T','\0'};
4216 if (strlenW(param1
) == 0) {
4218 if (GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &st
, NULL
,
4219 curtime
, sizeof(curtime
)/sizeof(WCHAR
))) {
4220 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME
), curtime
);
4221 if (strstrW (quals
, parmT
) == NULL
) {
4222 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
4223 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
4225 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
4229 else WCMD_print_error ();
4232 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
4236 /****************************************************************************
4239 * Shift batch parameters.
4240 * Optional /n says where to start shifting (n=0-8)
4243 void WCMD_shift (const WCHAR
*args
) {
4246 if (context
!= NULL
) {
4247 WCHAR
*pos
= strchrW(args
, '/');
4252 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
4253 start
= (*(pos
+1) - '0');
4255 SetLastError(ERROR_INVALID_PARAMETER
);
4260 WINE_TRACE("Shifting variables, starting at %d\n", start
);
4261 for (i
=start
;i
<=8;i
++) {
4262 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
4264 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
4269 /****************************************************************************
4272 void WCMD_start(const WCHAR
*args
)
4274 static const WCHAR exeW
[] = {'\\','c','o','m','m','a','n','d',
4275 '\\','s','t','a','r','t','.','e','x','e',0};
4276 WCHAR file
[MAX_PATH
];
4279 PROCESS_INFORMATION pi
;
4281 GetWindowsDirectoryW( file
, MAX_PATH
);
4282 strcatW( file
, exeW
);
4283 cmdline
= heap_alloc( (strlenW(file
) + strlenW(args
) + 2) * sizeof(WCHAR
) );
4284 strcpyW( cmdline
, file
);
4285 strcatW( cmdline
, spaceW
);
4286 strcatW( cmdline
, args
);
4288 memset( &st
, 0, sizeof(STARTUPINFOW
) );
4289 st
.cb
= sizeof(STARTUPINFOW
);
4291 if (CreateProcessW( file
, cmdline
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pi
))
4293 WaitForSingleObject( pi
.hProcess
, INFINITE
);
4294 GetExitCodeProcess( pi
.hProcess
, &errorlevel
);
4295 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
4296 CloseHandle(pi
.hProcess
);
4297 CloseHandle(pi
.hThread
);
4301 SetLastError(ERROR_FILE_NOT_FOUND
);
4302 WCMD_print_error ();
4308 /****************************************************************************
4311 * Set the console title
4313 void WCMD_title (const WCHAR
*args
) {
4314 SetConsoleTitleW(args
);
4317 /****************************************************************************
4320 * Copy a file to standard output.
4323 void WCMD_type (WCHAR
*args
) {
4327 BOOL writeHeaders
= FALSE
;
4329 if (param1
[0] == 0x00) {
4330 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
4334 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
4336 /* Loop through all args */
4339 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
4347 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
4348 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4349 FILE_ATTRIBUTE_NORMAL
, NULL
);
4350 if (h
== INVALID_HANDLE_VALUE
) {
4351 WCMD_print_error ();
4352 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
4356 static const WCHAR fmt
[] = {'\n','%','1','\n','\n','\0'};
4357 WCMD_output(fmt
, thisArg
);
4359 while (WCMD_ReadFile(h
, buffer
, sizeof(buffer
)/sizeof(WCHAR
) - 1, &count
)) {
4360 if (count
== 0) break; /* ReadFile reports success on EOF! */
4362 WCMD_output_asis (buffer
);
4369 /****************************************************************************
4372 * Output either a file or stdin to screen in pages
4375 void WCMD_more (WCHAR
*args
) {
4380 WCHAR moreStrPage
[100];
4383 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
4384 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
4385 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
4386 ')',' ','-','-','\n','\0'};
4387 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
4389 /* Prefix the NLS more with '-- ', then load the text */
4391 strcpyW(moreStr
, moreStart
);
4392 LoadStringW(hinst
, WCMD_MORESTR
, &moreStr
[3],
4393 (sizeof(moreStr
)/sizeof(WCHAR
))-3);
4395 if (param1
[0] == 0x00) {
4397 /* Wine implements pipes via temporary files, and hence stdin is
4398 effectively reading from the file. This means the prompts for
4399 more are satisfied by the next line from the input (file). To
4400 avoid this, ensure stdin is to the console */
4401 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
4402 HANDLE hConIn
= CreateFileW(conInW
, GENERIC_READ
| GENERIC_WRITE
,
4403 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4404 FILE_ATTRIBUTE_NORMAL
, 0);
4405 WINE_TRACE("No parms - working probably in pipe mode\n");
4406 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
4408 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4409 once you get in this bit unless due to a pipe, its going to end badly... */
4410 wsprintfW(moreStrPage
, moreFmt
, moreStr
);
4412 WCMD_enter_paged_mode(moreStrPage
);
4413 while (WCMD_ReadFile(hstdin
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
)) {
4414 if (count
== 0) break; /* ReadFile reports success on EOF! */
4416 WCMD_output_asis (buffer
);
4418 WCMD_leave_paged_mode();
4420 /* Restore stdin to what it was */
4421 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
4422 CloseHandle(hConIn
);
4426 BOOL needsPause
= FALSE
;
4428 /* Loop through all args */
4429 WINE_TRACE("Parms supplied - working through each file\n");
4430 WCMD_enter_paged_mode(moreStrPage
);
4433 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
4441 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, 100);
4442 WCMD_leave_paged_mode();
4443 WCMD_output_asis(moreStrPage
);
4444 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, sizeof(buffer
)/sizeof(WCHAR
), &count
);
4445 WCMD_enter_paged_mode(moreStrPage
);
4449 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
4450 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4451 FILE_ATTRIBUTE_NORMAL
, NULL
);
4452 if (h
== INVALID_HANDLE_VALUE
) {
4453 WCMD_print_error ();
4454 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
4458 ULONG64 fileLen
= 0;
4459 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
4461 /* Get the file size */
4462 GetFileAttributesExW(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
4463 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
4466 while (WCMD_ReadFile(h
, buffer
, (sizeof(buffer
)/sizeof(WCHAR
))-1, &count
)) {
4467 if (count
== 0) break; /* ReadFile reports success on EOF! */
4471 /* Update % count (would be used in WCMD_output_asis as prompt) */
4472 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
4474 WCMD_output_asis (buffer
);
4480 WCMD_leave_paged_mode();
4484 /****************************************************************************
4487 * Display verify flag.
4488 * FIXME: We don't actually do anything with the verify flag other than toggle
4492 void WCMD_verify (const WCHAR
*args
) {
4496 count
= strlenW(args
);
4498 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
4499 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
4502 if (lstrcmpiW(args
, onW
) == 0) {
4506 else if (lstrcmpiW(args
, offW
) == 0) {
4507 verify_mode
= FALSE
;
4510 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR
));
4513 /****************************************************************************
4516 * Display version info.
4519 void WCMD_version (void) {
4521 WCMD_output_asis (version_string
);
4525 /****************************************************************************
4528 * Display volume information (set_label = FALSE)
4529 * Additionally set volume label (set_label = TRUE)
4530 * Returns 1 on success, 0 otherwise
4533 int WCMD_volume(BOOL set_label
, const WCHAR
*path
)
4535 DWORD count
, serial
;
4536 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
4539 if (strlenW(path
) == 0) {
4540 status
= GetCurrentDirectoryW(sizeof(curdir
)/sizeof(WCHAR
), curdir
);
4542 WCMD_print_error ();
4545 status
= GetVolumeInformationW(NULL
, label
, sizeof(label
)/sizeof(WCHAR
),
4546 &serial
, NULL
, NULL
, NULL
, 0);
4549 static const WCHAR fmt
[] = {'%','s','\\','\0'};
4550 if ((path
[1] != ':') || (strlenW(path
) != 2)) {
4551 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
4554 wsprintfW (curdir
, fmt
, path
);
4555 status
= GetVolumeInformationW(curdir
, label
, sizeof(label
)/sizeof(WCHAR
),
4560 WCMD_print_error ();
4563 if (label
[0] != '\0') {
4564 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL
),
4568 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL
),
4571 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO
),
4572 HIWORD(serial
), LOWORD(serial
));
4574 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
4575 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, sizeof(string
)/sizeof(WCHAR
), &count
);
4577 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
4578 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
4580 if (strlenW(path
) != 0) {
4581 if (!SetVolumeLabelW(curdir
, string
)) WCMD_print_error ();
4584 if (!SetVolumeLabelW(NULL
, string
)) WCMD_print_error ();
4590 /**************************************************************************
4593 * Exit either the process, or just this batch program
4597 void WCMD_exit (CMD_LIST
**cmdList
) {
4599 static const WCHAR parmB
[] = {'/','B','\0'};
4600 int rc
= atoiW(param1
); /* Note: atoi of empty parameter is 0 */
4602 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
4604 context
-> skip_rest
= TRUE
;
4612 /*****************************************************************************
4615 * Lists or sets file associations (assoc = TRUE)
4616 * Lists or sets file types (assoc = FALSE)
4618 void WCMD_assoc (const WCHAR
*args
, BOOL assoc
) {
4621 DWORD accessOptions
= KEY_READ
;
4623 LONG rc
= ERROR_SUCCESS
;
4624 WCHAR keyValue
[MAXSTRING
];
4625 DWORD valueLen
= MAXSTRING
;
4627 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
4628 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4630 /* See if parameter includes '=' */
4632 newValue
= strchrW(args
, '=');
4633 if (newValue
) accessOptions
|= KEY_WRITE
;
4635 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4636 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, nullW
, 0,
4637 accessOptions
, &key
) != ERROR_SUCCESS
) {
4638 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4642 /* If no parameters then list all associations */
4643 if (*args
== 0x00) {
4646 /* Enumerate all the keys */
4647 while (rc
!= ERROR_NO_MORE_ITEMS
) {
4648 WCHAR keyName
[MAXSTRING
];
4651 /* Find the next value */
4652 nameLen
= MAXSTRING
;
4653 rc
= RegEnumKeyExW(key
, index
++, keyName
, &nameLen
, NULL
, NULL
, NULL
, NULL
);
4655 if (rc
== ERROR_SUCCESS
) {
4657 /* Only interested in extension ones if assoc, or others
4659 if ((keyName
[0] == '.' && assoc
) ||
4660 (!(keyName
[0] == '.') && (!assoc
)))
4662 WCHAR subkey
[MAXSTRING
];
4663 strcpyW(subkey
, keyName
);
4664 if (!assoc
) strcatW(subkey
, shOpCmdW
);
4666 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
4668 valueLen
= sizeof(keyValue
)/sizeof(WCHAR
);
4669 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
4670 WCMD_output_asis(keyName
);
4671 WCMD_output_asis(equalW
);
4672 /* If no default value found, leave line empty after '=' */
4673 if (rc
== ERROR_SUCCESS
) {
4674 WCMD_output_asis(keyValue
);
4676 WCMD_output_asis(newlineW
);
4677 RegCloseKey(readKey
);
4685 /* Parameter supplied - if no '=' on command line, its a query */
4686 if (newValue
== NULL
) {
4688 WCHAR subkey
[MAXSTRING
];
4690 /* Query terminates the parameter at the first space */
4691 strcpyW(keyValue
, args
);
4692 space
= strchrW(keyValue
, ' ');
4693 if (space
) *space
=0x00;
4695 /* Set up key name */
4696 strcpyW(subkey
, keyValue
);
4697 if (!assoc
) strcatW(subkey
, shOpCmdW
);
4699 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
4701 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
4702 WCMD_output_asis(args
);
4703 WCMD_output_asis(equalW
);
4704 /* If no default value found, leave line empty after '=' */
4705 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
4706 WCMD_output_asis(newlineW
);
4707 RegCloseKey(readKey
);
4710 WCHAR msgbuffer
[MAXSTRING
];
4712 /* Load the translated 'File association not found' */
4714 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
4716 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, sizeof(msgbuffer
)/sizeof(WCHAR
));
4718 WCMD_output_stderr(msgbuffer
, keyValue
);
4722 /* Not a query - its a set or clear of a value */
4725 WCHAR subkey
[MAXSTRING
];
4727 /* Get pointer to new value */
4731 /* Set up key name */
4732 strcpyW(subkey
, args
);
4733 if (!assoc
) strcatW(subkey
, shOpCmdW
);
4735 /* If nothing after '=' then clear value - only valid for ASSOC */
4736 if (*newValue
== 0x00) {
4738 if (assoc
) rc
= RegDeleteKeyW(key
, args
);
4739 if (assoc
&& rc
== ERROR_SUCCESS
) {
4740 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args
));
4742 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
4747 WCHAR msgbuffer
[MAXSTRING
];
4749 /* Load the translated 'File association not found' */
4751 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
,
4752 sizeof(msgbuffer
)/sizeof(WCHAR
));
4754 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
,
4755 sizeof(msgbuffer
)/sizeof(WCHAR
));
4757 WCMD_output_stderr(msgbuffer
, keyValue
);
4761 /* It really is a set value = contents */
4763 rc
= RegCreateKeyExW(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
4764 accessOptions
, NULL
, &readKey
, NULL
);
4765 if (rc
== ERROR_SUCCESS
) {
4766 rc
= RegSetValueExW(readKey
, NULL
, 0, REG_SZ
,
4768 sizeof(WCHAR
) * (strlenW(newValue
) + 1));
4769 RegCloseKey(readKey
);
4772 if (rc
!= ERROR_SUCCESS
) {
4776 WCMD_output_asis(args
);
4777 WCMD_output_asis(equalW
);
4778 WCMD_output_asis(newValue
);
4779 WCMD_output_asis(newlineW
);
4789 /****************************************************************************
4792 * Colors the terminal screen.
4795 void WCMD_color (void) {
4797 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
4798 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
4800 if (param1
[0] != 0x00 && strlenW(param1
) > 2) {
4801 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR
));
4805 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
4811 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
4816 /* Convert the color hex digits */
4817 if (param1
[0] == 0x00) {
4818 color
= defaultColor
;
4820 color
= strtoulW(param1
, NULL
, 16);
4823 /* Fail if fg == bg color */
4824 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
4829 /* Set the current screen contents and ensure all future writes
4830 remain this color */
4831 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
4832 SetConsoleTextAttribute(hStdOut
, color
);