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 inbuilt
[][10] = {
91 static const WCHAR externals
[][10] = {
96 static HINSTANCE hinst
;
97 struct env_stack
*saved_environment
;
98 static BOOL verify_mode
= FALSE
;
100 /* set /a routines work from single character operators, but some of the
101 operators are multiple character ones, especially the assignment ones.
102 Temporarily represent these using the values below on the operator stack */
103 #define OP_POSITIVE 'P'
104 #define OP_NEGATIVE 'N'
105 #define OP_ASSSIGNMUL 'a'
106 #define OP_ASSSIGNDIV 'b'
107 #define OP_ASSSIGNMOD 'c'
108 #define OP_ASSSIGNADD 'd'
109 #define OP_ASSSIGNSUB 'e'
110 #define OP_ASSSIGNAND 'f'
111 #define OP_ASSSIGNNOT 'g'
112 #define OP_ASSSIGNOR 'h'
113 #define OP_ASSSIGNSHL 'i'
114 #define OP_ASSSIGNSHR 'j'
116 /* This maintains a stack of operators, holding both the operator precedence
117 and the single character representation of the operator in question */
118 typedef struct _OPSTACK
122 struct _OPSTACK
*next
;
125 /* This maintains a stack of values, where each value can either be a
126 numeric value, or a string representing an environment variable */
127 typedef struct _VARSTACK
132 struct _VARSTACK
*next
;
135 /* This maintains a mapping between the calculated operator and the
136 single character representation for the assignment operators. */
141 } calcassignments
[] =
143 {'*', OP_ASSSIGNMUL
},
144 {'/', OP_ASSSIGNDIV
},
145 {'%', OP_ASSSIGNMOD
},
146 {'+', OP_ASSSIGNADD
},
147 {'-', OP_ASSSIGNSUB
},
148 {'&', OP_ASSSIGNAND
},
149 {'^', OP_ASSSIGNNOT
},
151 {'<', OP_ASSSIGNSHL
},
152 {'>', OP_ASSSIGNSHR
},
156 /**************************************************************************
159 * Issue a message and ask for confirmation, waiting on a valid answer.
161 * Returns True if Y (or A) answer is selected
162 * If optionAll contains a pointer, ALL is allowed, and if answered
166 static BOOL
WCMD_ask_confirm (const WCHAR
*message
, BOOL showSureText
,
170 WCHAR confirm
[MAXSTRING
];
171 WCHAR options
[MAXSTRING
];
172 WCHAR Ybuffer
[MAXSTRING
];
173 WCHAR Nbuffer
[MAXSTRING
];
174 WCHAR Abuffer
[MAXSTRING
];
175 WCHAR answer
[MAX_PATH
] = {'\0'};
178 /* Load the translated valid answers */
180 LoadStringW(hinst
, WCMD_CONFIRM
, confirm
, ARRAY_SIZE(confirm
));
181 msgid
= optionAll
? WCMD_YESNOALL
: WCMD_YESNO
;
182 LoadStringW(hinst
, msgid
, options
, ARRAY_SIZE(options
));
183 LoadStringW(hinst
, WCMD_YES
, Ybuffer
, ARRAY_SIZE(Ybuffer
));
184 LoadStringW(hinst
, WCMD_NO
, Nbuffer
, ARRAY_SIZE(Nbuffer
));
185 LoadStringW(hinst
, WCMD_ALL
, Abuffer
, ARRAY_SIZE(Abuffer
));
187 /* Loop waiting on a valid answer */
192 WCMD_output_asis (message
);
194 WCMD_output_asis (confirm
);
195 WCMD_output_asis (options
);
196 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, ARRAY_SIZE(answer
), &count
);
197 answer
[0] = towupper(answer
[0]);
198 if (answer
[0] == Ybuffer
[0])
200 if (answer
[0] == Nbuffer
[0])
202 if (optionAll
&& answer
[0] == Abuffer
[0])
210 /****************************************************************************
213 * Clear the terminal screen.
216 void WCMD_clear_screen (void) {
218 /* Emulate by filling the screen from the top left to bottom right with
219 spaces, then moving the cursor to the top left afterwards */
220 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
221 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
223 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
226 DWORD screenSize
, written
;
228 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
232 FillConsoleOutputCharacterW(hStdOut
, ' ', screenSize
, topLeft
, &written
);
233 FillConsoleOutputAttribute(hStdOut
, consoleInfo
.wAttributes
, screenSize
, topLeft
, &written
);
234 SetConsoleCursorPosition(hStdOut
, topLeft
);
238 /****************************************************************************
241 * Change the default i/o device (ie redirect STDin/STDout).
244 void WCMD_change_tty (void) {
246 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
250 /****************************************************************************
255 void WCMD_choice (const WCHAR
* args
) {
260 WCHAR
*my_command
= NULL
;
261 WCHAR opt_default
= 0;
262 DWORD opt_timeout
= 0;
269 have_console
= GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), &oldmode
);
272 my_command
= heap_strdupW(WCMD_skip_leading_spaces((WCHAR
*) args
));
274 ptr
= WCMD_skip_leading_spaces(my_command
);
275 while (*ptr
== '/') {
276 switch (towupper(ptr
[1])) {
279 /* the colon is optional */
283 if (!*ptr
|| iswspace(*ptr
)) {
284 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr
));
285 heap_free(my_command
);
289 /* remember the allowed keys (overwrite previous /C option) */
291 while (*ptr
&& (!iswspace(*ptr
)))
295 /* terminate allowed chars */
297 ptr
= WCMD_skip_leading_spaces(&ptr
[1]);
299 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c
));
304 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
309 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
314 /* the colon is optional */
318 opt_default
= *ptr
++;
320 if (!opt_default
|| (*ptr
!= ',')) {
321 WINE_FIXME("bad option %s for /T\n", opt_default
? wine_dbgstr_w(ptr
) : "");
322 heap_free(my_command
);
328 while (((answer
[count
] = *ptr
)) && iswdigit(*ptr
) && (count
< 15)) {
334 opt_timeout
= wcstol(answer
, NULL
, 10);
336 ptr
= WCMD_skip_leading_spaces(ptr
);
340 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr
));
341 heap_free(my_command
);
347 WINE_FIXME("timeout not supported: %c,%ld\n", opt_default
, opt_timeout
);
350 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), 0);
352 /* use default keys, when needed: localized versions of "Y"es and "No" */
354 LoadStringW(hinst
, WCMD_YES
, buffer
, ARRAY_SIZE(buffer
));
355 LoadStringW(hinst
, WCMD_NO
, buffer
+ 1, ARRAY_SIZE(buffer
) - 1);
360 /* print the question, when needed */
362 WCMD_output_asis(ptr
);
366 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c
));
370 /* print a list of all allowed answers inside brackets */
371 WCMD_output_asis(L
"[");
374 while ((answer
[0] = *ptr
++)) {
375 WCMD_output_asis(answer
);
377 WCMD_output_asis(L
",");
379 WCMD_output_asis(L
"]?");
384 /* FIXME: Add support for option /T */
385 answer
[1] = 0; /* terminate single character string */
386 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, 1, &count
);
389 answer
[0] = towupper(answer
[0]);
391 ptr
= wcschr(opt_c
, answer
[0]);
393 WCMD_output_asis(answer
);
394 WCMD_output_asis(L
"\r\n");
396 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), oldmode
);
398 errorlevel
= (ptr
- opt_c
) + 1;
399 WINE_TRACE("answer: %ld\n", errorlevel
);
400 heap_free(my_command
);
405 /* key not allowed: play the bell */
406 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer
));
407 WCMD_output_asis(L
"\a");
412 /****************************************************************************
415 * Adds an EOF onto the end of a file
416 * Returns TRUE on success
418 static BOOL
WCMD_AppendEOF(WCHAR
*filename
)
425 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename
));
426 h
= CreateFileW(filename
, GENERIC_WRITE
, 0, NULL
,
427 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
429 if (h
== INVALID_HANDLE_VALUE
) {
430 WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(filename
), GetLastError());
433 SetFilePointer (h
, 0, NULL
, FILE_END
);
434 if (!WriteFile(h
, &eof
, 1, &bytes_written
, NULL
)) {
435 WINE_ERR("Failed to append EOF to %s (%ld)\n", wine_dbgstr_w(filename
), GetLastError());
444 /****************************************************************************
447 * Checks if the two paths reference to the same file
449 static BOOL
WCMD_IsSameFile(const WCHAR
*name1
, const WCHAR
*name2
)
452 HANDLE file1
= INVALID_HANDLE_VALUE
, file2
= INVALID_HANDLE_VALUE
;
453 BY_HANDLE_FILE_INFORMATION info1
, info2
;
455 file1
= CreateFileW(name1
, 0, FILE_SHARE_DELETE
| FILE_SHARE_READ
| FILE_SHARE_WRITE
, 0, OPEN_EXISTING
, 0, 0);
456 if (file1
== INVALID_HANDLE_VALUE
|| !GetFileInformationByHandle(file1
, &info1
))
459 file2
= CreateFileW(name2
, 0, FILE_SHARE_DELETE
| FILE_SHARE_READ
| FILE_SHARE_WRITE
, 0, OPEN_EXISTING
, 0, 0);
460 if (file2
== INVALID_HANDLE_VALUE
|| !GetFileInformationByHandle(file2
, &info2
))
463 ret
= info1
.dwVolumeSerialNumber
== info2
.dwVolumeSerialNumber
464 && info1
.nFileIndexHigh
== info2
.nFileIndexHigh
465 && info1
.nFileIndexLow
== info2
.nFileIndexLow
;
467 if (file1
!= INVALID_HANDLE_VALUE
)
469 if (file2
!= INVALID_HANDLE_VALUE
)
474 /****************************************************************************
478 * optionally reading only until EOF (ascii copy)
479 * optionally appending onto an existing file (append)
480 * Returns TRUE on success
482 static BOOL
WCMD_ManualCopy(WCHAR
*srcname
, WCHAR
*dstname
, BOOL ascii
, BOOL append
)
486 DWORD bytesread
, byteswritten
;
488 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
489 wine_dbgstr_w(srcname
), wine_dbgstr_w(dstname
), append
);
491 in
= CreateFileW(srcname
, GENERIC_READ
, 0, NULL
,
492 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
493 if (in
== INVALID_HANDLE_VALUE
) {
494 WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(srcname
), GetLastError());
498 /* Open the output file, overwriting if not appending */
499 out
= CreateFileW(dstname
, GENERIC_WRITE
, 0, NULL
,
500 append
?OPEN_EXISTING
:CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
501 if (out
== INVALID_HANDLE_VALUE
) {
502 WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(dstname
), GetLastError());
507 /* Move to end of destination if we are going to append to it */
509 SetFilePointer(out
, 0, NULL
, FILE_END
);
512 /* Loop copying data from source to destination until EOF read */
515 char buffer
[MAXSTRING
];
517 ok
= ReadFile(in
, buffer
, MAXSTRING
, &bytesread
, NULL
);
520 /* Stop at first EOF */
522 char *ptr
= (char *)memchr((void *)buffer
, '\x1a', bytesread
);
523 if (ptr
) bytesread
= (ptr
- buffer
);
527 ok
= WriteFile(out
, buffer
, bytesread
, &byteswritten
, NULL
);
528 if (!ok
|| byteswritten
!= bytesread
) {
529 WINE_ERR("Unexpected failure writing to %s, rc=%ld\n",
530 wine_dbgstr_w(dstname
), GetLastError());
534 WINE_ERR("Unexpected failure reading from %s, rc=%ld\n",
535 wine_dbgstr_w(srcname
), GetLastError());
537 } while (ok
&& bytesread
> 0);
544 /****************************************************************************
547 * Copy a file or wildcarded set.
548 * For ascii/binary type copies, it gets complex:
549 * Syntax on command line is
550 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
551 * Where first /a or /b sets 'mode in operation' until another is found
552 * once another is found, it applies to the file preceding the /a or /b
553 * In addition each filename can contain wildcards
554 * To make matters worse, the + may be in the same parameter (i.e. no
555 * whitespace) or with whitespace separating it
557 * ASCII mode on read == read and stop at first EOF
558 * ASCII mode on write == append EOF to destination
559 * Binary == copy as-is
561 * Design of this is to build up a list of files which will be copied into a
562 * list, then work through the list file by file.
563 * If no destination is specified, it defaults to the name of the first file in
564 * the list, but the current directory.
568 void WCMD_copy(WCHAR
* args
) {
570 BOOL opt_d
, opt_v
, opt_n
, opt_z
, opt_y
, opt_noty
;
575 HANDLE hff
= INVALID_HANDLE_VALUE
;
576 int binarymode
= -1; /* -1 means use the default, 1 is binary, 0 ascii */
577 BOOL concatnextfilename
= FALSE
; /* True if we have just processed a + */
578 BOOL anyconcats
= FALSE
; /* Have we found any + options */
579 BOOL appendfirstsource
= FALSE
; /* Use first found filename as destination */
580 BOOL writtenoneconcat
= FALSE
; /* Remember when the first concatenated file done */
581 BOOL prompt
; /* Prompt before overwriting */
582 WCHAR destname
[MAX_PATH
]; /* Used in calculating the destination name */
583 BOOL destisdirectory
= FALSE
; /* Is the destination a directory? */
587 BOOL dstisdevice
= FALSE
;
589 typedef struct _COPY_FILES
591 struct _COPY_FILES
*next
;
596 COPY_FILES
*sourcelist
= NULL
;
597 COPY_FILES
*lastcopyentry
= NULL
;
598 COPY_FILES
*destination
= NULL
;
599 COPY_FILES
*thiscopy
= NULL
;
600 COPY_FILES
*prevcopy
= NULL
;
602 /* Assume we were successful! */
605 /* If no args supplied at all, report an error */
606 if (param1
[0] == 0x00) {
607 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG
));
612 opt_d
= opt_v
= opt_n
= opt_z
= opt_y
= opt_noty
= FALSE
;
614 /* Walk through all args, building up a list of files to process */
615 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
616 while (*(thisparam
)) {
620 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam
));
622 /* Handle switches */
623 if (*thisparam
== '/') {
624 while (*thisparam
== '/') {
626 if (towupper(*thisparam
) == 'D') {
628 if (opt_d
) WINE_FIXME("copy /D support not implemented yet\n");
629 } else if (towupper(*thisparam
) == 'Y') {
631 } else if (towupper(*thisparam
) == '-' && towupper(*(thisparam
+1)) == 'Y') {
633 } else if (towupper(*thisparam
) == 'V') {
635 if (opt_v
) WINE_FIXME("copy /V support not implemented yet\n");
636 } else if (towupper(*thisparam
) == 'N') {
638 if (opt_n
) WINE_FIXME("copy /N support not implemented yet\n");
639 } else if (towupper(*thisparam
) == 'Z') {
641 if (opt_z
) WINE_FIXME("copy /Z support not implemented yet\n");
642 } else if (towupper(*thisparam
) == 'A') {
643 if (binarymode
!= 0) {
645 WINE_TRACE("Subsequent files will be handled as ASCII\n");
646 if (destination
!= NULL
) {
647 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination
->name
));
648 destination
->binarycopy
= binarymode
;
649 } else if (lastcopyentry
!= NULL
) {
650 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry
->name
));
651 lastcopyentry
->binarycopy
= binarymode
;
654 } else if (towupper(*thisparam
) == 'B') {
655 if (binarymode
!= 1) {
657 WINE_TRACE("Subsequent files will be handled as binary\n");
658 if (destination
!= NULL
) {
659 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination
->name
));
660 destination
->binarycopy
= binarymode
;
661 } else if (lastcopyentry
!= NULL
) {
662 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry
->name
));
663 lastcopyentry
->binarycopy
= binarymode
;
667 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam
));
672 /* This parameter was purely switches, get the next one */
673 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
677 /* We have found something which is not a switch. If could be anything of the form
678 sourcefilename (which could be destination too)
679 + (when filename + filename syntex used)
680 sourcefilename+sourcefilename
682 +/b[tests show windows then ignores to end of parameter]
685 if (*thisparam
=='+') {
686 if (lastcopyentry
== NULL
) {
687 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
691 concatnextfilename
= TRUE
;
695 /* Move to next thing to process */
697 if (*thisparam
== 0x00)
698 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
702 /* We have found something to process - build a COPY_FILE block to store it */
703 thiscopy
= heap_xalloc(sizeof(COPY_FILES
));
705 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam
));
706 thiscopy
->concatenate
= concatnextfilename
;
707 thiscopy
->binarycopy
= binarymode
;
708 thiscopy
->next
= NULL
;
710 /* Time to work out the name. Allocate at least enough space (deliberately too much to
711 leave space to append \* to the end) , then copy in character by character. Strip off
712 quotes if we find them. */
713 len
= lstrlenW(thisparam
) + (sizeof(WCHAR
) * 5); /* 5 spare characters, null + \*.* */
714 thiscopy
->name
= heap_xalloc(len
*sizeof(WCHAR
));
715 memset(thiscopy
->name
, 0x00, len
);
718 pos2
= thiscopy
->name
;
720 while (*pos1
&& (inquotes
|| (*pos1
!= '+' && *pos1
!= '/'))) {
722 inquotes
= !inquotes
;
724 } else *pos2
++ = *pos1
++;
727 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy
->name
));
729 /* This is either the first source, concatenated subsequent source or destination */
730 if (sourcelist
== NULL
) {
731 WINE_TRACE("Adding as first source part\n");
732 sourcelist
= thiscopy
;
733 lastcopyentry
= thiscopy
;
734 } else if (concatnextfilename
) {
735 WINE_TRACE("Adding to source file list to be concatenated\n");
736 lastcopyentry
->next
= thiscopy
;
737 lastcopyentry
= thiscopy
;
738 } else if (destination
== NULL
) {
739 destination
= thiscopy
;
741 /* We have processed sources and destinations and still found more to do - invalid */
742 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
746 concatnextfilename
= FALSE
;
748 /* We either need to process the rest of the parameter or move to the next */
749 if (*pos1
== '/' || *pos1
== '+') {
753 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
757 /* Ensure we have at least one source file */
759 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
764 /* Default whether automatic overwriting is on. If we are interactive then
765 we prompt by default, otherwise we overwrite by default
766 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
767 if (opt_noty
) prompt
= TRUE
;
768 else if (opt_y
) prompt
= FALSE
;
770 /* By default, we will force the overwrite in batch mode and ask for
771 * confirmation in interactive mode. */
772 prompt
= interactive
;
773 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
774 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
775 * default behavior. */
776 len
= GetEnvironmentVariableW(L
"COPYCMD", copycmd
, ARRAY_SIZE(copycmd
));
777 if (len
&& len
< ARRAY_SIZE(copycmd
)) {
778 if (!lstrcmpiW(copycmd
, L
"/Y"))
780 else if (!lstrcmpiW(copycmd
, L
"/-Y"))
785 /* Calculate the destination now - if none supplied, it's current dir +
786 filename of first file in list*/
787 if (destination
== NULL
) {
789 WINE_TRACE("No destination supplied, so need to calculate it\n");
790 lstrcpyW(destname
, L
".");
791 lstrcatW(destname
, L
"\\");
793 destination
= heap_xalloc(sizeof(COPY_FILES
));
794 if (destination
== NULL
) goto exitreturn
;
795 destination
->concatenate
= FALSE
; /* Not used for destination */
796 destination
->binarycopy
= binarymode
;
797 destination
->next
= NULL
; /* Not used for destination */
798 destination
->name
= NULL
; /* To be filled in */
799 destisdirectory
= TRUE
;
805 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
807 /* Convert to fully qualified path/filename */
808 if (!WCMD_get_fullpath(destination
->name
, ARRAY_SIZE(destname
), destname
, &filenamepart
)) return;
809 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname
));
811 /* If parameter is a directory, ensure it ends in \ */
812 attributes
= GetFileAttributesW(destname
);
813 if (ends_with_backslash( destname
) ||
814 ((attributes
!= INVALID_FILE_ATTRIBUTES
) &&
815 (attributes
& FILE_ATTRIBUTE_DIRECTORY
))) {
817 destisdirectory
= TRUE
;
818 if (!ends_with_backslash(destname
)) lstrcatW(destname
, L
"\\");
819 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname
));
823 /* Normally, the destination is the current directory unless we are
824 concatenating, in which case it's current directory plus first filename.
826 In addition by default it is a binary copy unless concatenating, when
827 the copy defaults to an ascii copy (stop at EOF). We do not know the
828 first source part yet (until we search) so flag as needing filling in. */
831 /* We have found an a+b type syntax, so destination has to be a filename
832 and we need to default to ascii copying. If we have been supplied a
833 directory as the destination, we need to defer calculating the name */
834 if (destisdirectory
) appendfirstsource
= TRUE
;
835 if (destination
->binarycopy
== -1) destination
->binarycopy
= 0;
837 } else if (!destisdirectory
) {
838 /* We have been asked to copy to a filename. Default to ascii IF the
839 source contains wildcards (true even if only one match) */
840 if (wcspbrk(sourcelist
->name
, L
"*?") != NULL
) {
841 anyconcats
= TRUE
; /* We really are concatenating to a single file */
842 if (destination
->binarycopy
== -1) {
843 destination
->binarycopy
= 0;
846 if (destination
->binarycopy
== -1) {
847 destination
->binarycopy
= 1;
852 /* Save away the destination name*/
853 heap_free(destination
->name
);
854 destination
->name
= heap_strdupW(destname
);
855 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
856 wine_dbgstr_w(destname
), appendfirstsource
);
858 /* Remember if the destination is a device */
859 if (wcsncmp(destination
->name
, L
"\\\\.\\", lstrlenW(L
"\\\\.\\")) == 0) {
860 WINE_TRACE("Destination is a device\n");
864 /* Now we need to walk the set of sources, and process each name we come to.
865 If anyconcats is true, we are writing to one file, otherwise we are using
866 the source name each time.
867 If destination exists, prompt for overwrite the first time (if concatenating
868 we ask each time until yes is answered)
869 The first source file we come across must exist (when wildcards expanded)
870 and if concatenating with overwrite prompts, each source file must exist
871 until a yes is answered. */
873 thiscopy
= sourcelist
;
876 while (thiscopy
!= NULL
) {
878 WCHAR srcpath
[MAX_PATH
];
879 const WCHAR
*srcname
;
882 BOOL srcisdevice
= FALSE
;
884 /* If it was not explicit, we now know whether we are concatenating or not and
885 hence whether to copy as binary or ascii */
886 if (thiscopy
->binarycopy
== -1) thiscopy
->binarycopy
= !anyconcats
;
888 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
889 to where the filename portion begins (used for wildcard expansion). */
890 if (!WCMD_get_fullpath(thiscopy
->name
, ARRAY_SIZE(srcpath
), srcpath
, &filenamepart
)) return;
891 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath
));
893 /* If parameter is a directory, ensure it ends in \* */
894 attributes
= GetFileAttributesW(srcpath
);
895 if (ends_with_backslash( srcpath
)) {
897 /* We need to know where the filename part starts, so append * and
898 recalculate the full resulting path */
899 lstrcatW(thiscopy
->name
, L
"*");
900 if (!WCMD_get_fullpath(thiscopy
->name
, ARRAY_SIZE(srcpath
), srcpath
, &filenamepart
)) return;
901 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath
));
903 } else if ((wcspbrk(srcpath
, L
"*?") == NULL
) &&
904 (attributes
!= INVALID_FILE_ATTRIBUTES
) &&
905 (attributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
907 /* We need to know where the filename part starts, so append \* and
908 recalculate the full resulting path */
909 lstrcatW(thiscopy
->name
, L
"\\*");
910 if (!WCMD_get_fullpath(thiscopy
->name
, ARRAY_SIZE(srcpath
), srcpath
, &filenamepart
)) return;
911 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath
));
914 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
915 wine_dbgstr_w(srcpath
), anyconcats
);
917 /* If the source is a device, just use it, otherwise search */
918 if (wcsncmp(srcpath
, L
"\\\\.\\", lstrlenW(L
"\\\\.\\")) == 0) {
919 WINE_TRACE("Source is a device\n");
921 srcname
= &srcpath
[4]; /* After the \\.\ prefix */
924 /* Loop through all source files */
925 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath
));
926 hff
= FindFirstFileW(srcpath
, &fd
);
927 if (hff
!= INVALID_HANDLE_VALUE
) {
928 srcname
= fd
.cFileName
;
932 if (srcisdevice
|| hff
!= INVALID_HANDLE_VALUE
) {
934 WCHAR outname
[MAX_PATH
];
936 BOOL appendtofirstfile
= FALSE
;
938 /* Skip . and .., and directories */
939 if (!srcisdevice
&& fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
940 WINE_TRACE("Skipping directories\n");
943 /* Build final destination name */
944 lstrcpyW(outname
, destination
->name
);
945 if (destisdirectory
|| appendfirstsource
) lstrcatW(outname
, srcname
);
947 /* Build source name */
948 if (!srcisdevice
) lstrcpyW(filenamepart
, srcname
);
950 /* Do we just overwrite (we do if we are writing to a device) */
952 if (dstisdevice
|| (anyconcats
&& writtenoneconcat
)) {
956 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath
));
957 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname
));
958 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
959 thiscopy
->binarycopy
, destination
->binarycopy
, overwrite
, prompt
);
961 if (!writtenoneconcat
) {
962 appendtofirstfile
= anyconcats
&& WCMD_IsSameFile(srcpath
, outname
);
965 /* Prompt before overwriting */
966 if (appendtofirstfile
) {
968 } else if (!overwrite
) {
969 DWORD attributes
= GetFileAttributesW(outname
);
970 if (attributes
!= INVALID_FILE_ATTRIBUTES
) {
972 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), outname
);
973 overwrite
= WCMD_ask_confirm(question
, FALSE
, NULL
);
976 else overwrite
= TRUE
;
979 /* If we needed to save away the first filename, do it */
980 if (appendfirstsource
&& overwrite
) {
981 heap_free(destination
->name
);
982 destination
->name
= heap_strdupW(outname
);
983 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname
));
984 appendfirstsource
= FALSE
;
985 destisdirectory
= FALSE
;
988 /* Do the copy as appropriate */
990 if (anyconcats
&& WCMD_IsSameFile(srcpath
, outname
)) {
991 /* Silently skip if the destination file is also a source file */
993 } else if (anyconcats
&& writtenoneconcat
) {
994 if (thiscopy
->binarycopy
) {
995 status
= WCMD_ManualCopy(srcpath
, outname
, FALSE
, TRUE
);
997 status
= WCMD_ManualCopy(srcpath
, outname
, TRUE
, TRUE
);
999 } else if (!thiscopy
->binarycopy
) {
1000 status
= WCMD_ManualCopy(srcpath
, outname
, TRUE
, FALSE
);
1001 } else if (srcisdevice
) {
1002 status
= WCMD_ManualCopy(srcpath
, outname
, FALSE
, FALSE
);
1004 status
= CopyFileW(srcpath
, outname
, FALSE
);
1007 WCMD_print_error ();
1010 WINE_TRACE("Copied successfully\n");
1011 if (anyconcats
) writtenoneconcat
= TRUE
;
1013 /* Append EOF if ascii destination and we are not going to add more onto the end
1014 Note: Testing shows windows has an optimization whereas if you have a binary
1015 copy of a file to a single destination (ie concatenation) then it does not add
1016 the EOF, hence the check on the source copy type below. */
1017 if (!destination
->binarycopy
&& !anyconcats
&& !thiscopy
->binarycopy
) {
1018 if (!WCMD_AppendEOF(outname
)) {
1019 WCMD_print_error ();
1026 } while (!srcisdevice
&& FindNextFileW(hff
, &fd
) != 0);
1027 if (!srcisdevice
) FindClose (hff
);
1029 /* Error if the first file was not found */
1030 if (!anyconcats
|| !writtenoneconcat
) {
1031 WCMD_print_error ();
1036 /* Step on to the next supplied source */
1037 thiscopy
= thiscopy
-> next
;
1040 /* Append EOF if ascii destination and we were concatenating */
1041 if (!errorlevel
&& !destination
->binarycopy
&& anyconcats
&& writtenoneconcat
) {
1042 if (!WCMD_AppendEOF(destination
->name
)) {
1043 WCMD_print_error ();
1048 /* Exit out of the routine, freeing any remaining allocated memory */
1051 thiscopy
= sourcelist
;
1052 while (thiscopy
!= NULL
) {
1053 prevcopy
= thiscopy
;
1054 /* Free up this block*/
1055 thiscopy
= thiscopy
-> next
;
1056 heap_free(prevcopy
->name
);
1057 heap_free(prevcopy
);
1060 /* Free up the destination memory */
1062 heap_free(destination
->name
);
1063 heap_free(destination
);
1069 /****************************************************************************
1072 * Create a directory (and, if needed, any intermediate directories).
1074 * Modifies its argument by replacing slashes temporarily with nulls.
1077 static BOOL
create_full_path(WCHAR
* path
)
1081 /* don't mess with drive letter portion of path, if any */
1086 /* Strip trailing slashes. */
1087 for (p
= path
+ lstrlenW(path
) - 1; p
!= start
&& *p
== '\\'; p
--)
1090 /* Step through path, creating intermediate directories as needed. */
1091 /* First component includes drive letter, if any. */
1095 /* Skip to end of component */
1096 while (*p
== '\\') p
++;
1097 while (*p
&& *p
!= '\\') p
++;
1099 /* path is now the original full path */
1100 return CreateDirectoryW(path
, NULL
);
1102 /* Truncate path, create intermediate directory, and restore path */
1104 rv
= CreateDirectoryW(path
, NULL
);
1106 if (!rv
&& GetLastError() != ERROR_ALREADY_EXISTS
)
1113 void WCMD_create_dir (WCHAR
*args
) {
1117 if (param1
[0] == 0x00) {
1118 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1121 /* Loop through all args */
1123 WCHAR
*thisArg
= WCMD_parameter(args
, argno
++, &argN
, FALSE
, FALSE
);
1125 if (!create_full_path(thisArg
)) {
1126 WCMD_print_error ();
1132 /* Parse the /A options given by the user on the commandline
1133 * into a bitmask of wanted attributes (*wantSet),
1134 * and a bitmask of unwanted attributes (*wantClear).
1136 static void WCMD_delete_parse_attributes(DWORD
*wantSet
, DWORD
*wantClear
) {
1139 /* both are strictly 'out' parameters */
1143 /* For each /A argument */
1144 for (p
=wcsstr(quals
, L
"/A"); p
!= NULL
; p
=wcsstr(p
, L
"/A")) {
1145 /* Skip /A itself */
1148 /* Skip optional : */
1151 /* For each of the attribute specifier chars to this /A option */
1152 for (; *p
!= 0 && *p
!= '/'; p
++) {
1153 BOOL negate
= FALSE
;
1161 /* Convert the attribute specifier to a bit in one of the masks */
1163 case 'R': mask
= FILE_ATTRIBUTE_READONLY
; break;
1164 case 'H': mask
= FILE_ATTRIBUTE_HIDDEN
; break;
1165 case 'S': mask
= FILE_ATTRIBUTE_SYSTEM
; break;
1166 case 'A': mask
= FILE_ATTRIBUTE_ARCHIVE
; break;
1168 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
1178 /* If filename part of parameter is * or *.*,
1179 * and neither /Q nor /P options were given,
1180 * prompt the user whether to proceed.
1181 * Returns FALSE if user says no, TRUE otherwise.
1182 * *pPrompted is set to TRUE if the user is prompted.
1183 * (If /P supplied, del will prompt for individual files later.)
1185 static BOOL
WCMD_delete_confirm_wildcard(const WCHAR
*filename
, BOOL
*pPrompted
) {
1186 if ((wcsstr(quals
, L
"/Q") == NULL
) && (wcsstr(quals
, L
"/P") == NULL
)) {
1188 WCHAR dir
[MAX_PATH
];
1189 WCHAR fname
[MAX_PATH
];
1190 WCHAR ext
[MAX_PATH
];
1191 WCHAR fpath
[MAX_PATH
];
1193 /* Convert path into actual directory spec */
1194 if (!WCMD_get_fullpath(filename
, ARRAY_SIZE(fpath
), fpath
, NULL
)) return FALSE
;
1195 _wsplitpath(fpath
, drive
, dir
, fname
, ext
);
1197 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1198 if ((lstrcmpW(fname
, L
"*") == 0) && (*ext
== 0x00 || (lstrcmpW(ext
, L
".*") == 0))) {
1200 WCHAR question
[MAXSTRING
];
1202 /* Caller uses this to suppress "file not found" warning later */
1205 /* Ask for confirmation */
1206 wsprintfW(question
, L
"%s ", fpath
);
1207 return WCMD_ask_confirm(question
, TRUE
, NULL
);
1210 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1214 /* Helper function for WCMD_delete().
1215 * Deletes a single file, directory, or wildcard.
1216 * If /S was given, does it recursively.
1217 * Returns TRUE if a file was deleted.
1219 static BOOL
WCMD_delete_one (const WCHAR
*thisArg
) {
1221 DWORD unwanted_attrs
;
1223 WCHAR argCopy
[MAX_PATH
];
1224 WIN32_FIND_DATAW fd
;
1226 WCHAR fpath
[MAX_PATH
];
1228 BOOL handleParm
= TRUE
;
1230 WCMD_delete_parse_attributes(&wanted_attrs
, &unwanted_attrs
);
1232 lstrcpyW(argCopy
, thisArg
);
1233 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1234 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
1236 if (!WCMD_delete_confirm_wildcard(argCopy
, &found
)) {
1237 /* Skip this arg if user declines to delete *.* */
1241 /* First, try to delete in the current directory */
1242 hff
= FindFirstFileW(argCopy
, &fd
);
1243 if (hff
== INVALID_HANDLE_VALUE
) {
1249 /* Support del <dirname> by just deleting all files dirname\* */
1251 && (wcschr(argCopy
,'*') == NULL
)
1252 && (wcschr(argCopy
,'?') == NULL
)
1253 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
1255 WCHAR modifiedParm
[MAX_PATH
];
1257 lstrcpyW(modifiedParm
, argCopy
);
1258 lstrcatW(modifiedParm
, L
"\\*");
1261 WCMD_delete_one(modifiedParm
);
1263 } else if (handleParm
) {
1265 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1266 lstrcpyW (fpath
, argCopy
);
1268 p
= wcsrchr (fpath
, '\\');
1271 lstrcatW (fpath
, fd
.cFileName
);
1273 else lstrcpyW (fpath
, fd
.cFileName
);
1274 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
1277 /* Handle attribute matching (/A) */
1278 ok
= ((fd
.dwFileAttributes
& wanted_attrs
) == wanted_attrs
)
1279 && ((fd
.dwFileAttributes
& unwanted_attrs
) == 0);
1281 /* /P means prompt for each file */
1282 if (ok
&& wcsstr(quals
, L
"/P") != NULL
) {
1285 /* Ask for confirmation */
1286 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
1287 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1288 LocalFree(question
);
1291 /* Only proceed if ok to */
1294 /* If file is read only, and /A:r or /F supplied, delete it */
1295 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
1296 ((wanted_attrs
& FILE_ATTRIBUTE_READONLY
) ||
1297 wcsstr(quals
, L
"/F") != NULL
)) {
1298 SetFileAttributesW(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
1301 /* Now do the delete */
1302 if (!DeleteFileW(fpath
)) WCMD_print_error ();
1306 } while (FindNextFileW(hff
, &fd
) != 0);
1310 /* Now recurse into all subdirectories handling the parameter in the same way */
1311 if (wcsstr(quals
, L
"/S") != NULL
) {
1313 WCHAR thisDir
[MAX_PATH
];
1317 WCHAR dir
[MAX_PATH
];
1318 WCHAR fname
[MAX_PATH
];
1319 WCHAR ext
[MAX_PATH
];
1321 /* Convert path into actual directory spec */
1322 if (!WCMD_get_fullpath(argCopy
, ARRAY_SIZE(thisDir
), thisDir
, NULL
)) return FALSE
;
1324 _wsplitpath(thisDir
, drive
, dir
, fname
, ext
);
1326 lstrcpyW(thisDir
, drive
);
1327 lstrcatW(thisDir
, dir
);
1328 cPos
= lstrlenW(thisDir
);
1330 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
1332 /* Append '*' to the directory */
1333 thisDir
[cPos
] = '*';
1334 thisDir
[cPos
+1] = 0x00;
1336 hff
= FindFirstFileW(thisDir
, &fd
);
1338 /* Remove residual '*' */
1339 thisDir
[cPos
] = 0x00;
1341 if (hff
!= INVALID_HANDLE_VALUE
) {
1342 DIRECTORY_STACK
*allDirs
= NULL
;
1343 DIRECTORY_STACK
*lastEntry
= NULL
;
1346 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
1347 (lstrcmpW(fd
.cFileName
, L
"..") != 0) && (lstrcmpW(fd
.cFileName
, L
".") != 0)) {
1349 DIRECTORY_STACK
*nextDir
;
1350 WCHAR subParm
[MAX_PATH
];
1352 /* Work out search parameter in sub dir */
1353 lstrcpyW (subParm
, thisDir
);
1354 lstrcatW (subParm
, fd
.cFileName
);
1355 lstrcatW (subParm
, L
"\\");
1356 lstrcatW (subParm
, fname
);
1357 lstrcatW (subParm
, ext
);
1358 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
1360 /* Allocate memory, add to list */
1361 nextDir
= heap_xalloc(sizeof(DIRECTORY_STACK
));
1362 if (allDirs
== NULL
) allDirs
= nextDir
;
1363 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
1364 lastEntry
= nextDir
;
1365 nextDir
->next
= NULL
;
1366 nextDir
->dirName
= heap_strdupW(subParm
);
1368 } while (FindNextFileW(hff
, &fd
) != 0);
1371 /* Go through each subdir doing the delete */
1372 while (allDirs
!= NULL
) {
1373 DIRECTORY_STACK
*tempDir
;
1375 tempDir
= allDirs
->next
;
1376 found
|= WCMD_delete_one (allDirs
->dirName
);
1378 heap_free(allDirs
->dirName
);
1388 /****************************************************************************
1391 * Delete a file or wildcarded set.
1394 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1395 * - Each set is a pattern, eg /ahr /as-r means
1396 * readonly+hidden OR nonreadonly system files
1397 * - The '-' applies to a single field, ie /a:-hr means read only
1401 BOOL
WCMD_delete (WCHAR
*args
) {
1404 BOOL argsProcessed
= FALSE
;
1405 BOOL foundAny
= FALSE
;
1409 for (argno
=0; ; argno
++) {
1414 thisArg
= WCMD_parameter (args
, argno
, &argN
, FALSE
, FALSE
);
1416 break; /* no more parameters */
1418 continue; /* skip options */
1420 argsProcessed
= TRUE
;
1421 found
= WCMD_delete_one(thisArg
);
1423 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND
), thisArg
);
1427 /* Handle no valid args */
1429 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1437 * Returns a trimmed version of s with all leading and trailing whitespace removed
1441 static WCHAR
*WCMD_strtrim(const WCHAR
*s
)
1443 DWORD len
= lstrlenW(s
);
1444 const WCHAR
*start
= s
;
1447 result
= heap_xalloc((len
+ 1) * sizeof(WCHAR
));
1449 while (iswspace(*start
)) start
++;
1451 const WCHAR
*end
= s
+ len
- 1;
1452 while (end
> start
&& iswspace(*end
)) end
--;
1453 memcpy(result
, start
, (end
- start
+ 2) * sizeof(WCHAR
));
1454 result
[end
- start
+ 1] = '\0';
1462 /****************************************************************************
1465 * Echo input to the screen (or not). We don't try to emulate the bugs
1466 * in DOS (try typing "ECHO ON AGAIN" for an example).
1469 void WCMD_echo (const WCHAR
*args
)
1472 const WCHAR
*origcommand
= args
;
1475 if ( args
[0]==' ' || args
[0]=='\t' || args
[0]=='.'
1476 || args
[0]==':' || args
[0]==';' || args
[0]=='/')
1479 trimmed
= WCMD_strtrim(args
);
1480 if (!trimmed
) return;
1482 count
= lstrlenW(trimmed
);
1483 if (count
== 0 && origcommand
[0]!='.' && origcommand
[0]!=':'
1484 && origcommand
[0]!=';' && origcommand
[0]!='/') {
1485 if (echo_mode
) WCMD_output(WCMD_LoadMessage(WCMD_ECHOPROMPT
), L
"ON");
1486 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), L
"OFF");
1491 if (lstrcmpiW(trimmed
, L
"ON") == 0)
1493 else if (lstrcmpiW(trimmed
, L
"OFF") == 0)
1496 WCMD_output_asis (args
);
1497 WCMD_output_asis(L
"\r\n");
1502 /*****************************************************************************
1505 * Execute a command, and any && or bracketed follow on to the command. The
1506 * first command to be executed may not be at the front of the
1507 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1509 static void WCMD_part_execute(CMD_LIST
**cmdList
, const WCHAR
*firstcmd
,
1510 BOOL isIF
, BOOL executecmds
)
1512 CMD_LIST
*curPosition
= *cmdList
;
1513 int myDepth
= (*cmdList
)->bracketDepth
;
1515 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d), isIF(%d)\n", cmdList
,
1516 wine_dbgstr_w(firstcmd
), executecmds
, isIF
);
1518 /* Skip leading whitespace between condition and the command */
1519 while (firstcmd
&& *firstcmd
&& (*firstcmd
==' ' || *firstcmd
=='\t')) firstcmd
++;
1521 /* Process the first command, if there is one */
1522 if (executecmds
&& firstcmd
&& *firstcmd
) {
1523 WCHAR
*command
= heap_strdupW(firstcmd
);
1524 WCMD_execute (firstcmd
, (*cmdList
)->redirects
, cmdList
, FALSE
);
1529 /* If it didn't move the position, step to next command */
1530 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1532 /* Process any other parts of the command */
1534 BOOL processThese
= executecmds
;
1537 /* execute all appropriate commands */
1538 curPosition
= *cmdList
;
1540 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d) processThese(%d)\n",
1542 (*cmdList
)->prevDelim
,
1543 (*cmdList
)->bracketDepth
,
1547 /* Execute any statements appended to the line */
1548 /* FIXME: Only if previous call worked for && or failed for || */
1549 if ((*cmdList
)->prevDelim
== CMD_ONFAILURE
||
1550 (*cmdList
)->prevDelim
== CMD_ONSUCCESS
) {
1551 if (processThese
&& (*cmdList
)->command
) {
1552 WCMD_execute ((*cmdList
)->command
, (*cmdList
)->redirects
,
1555 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1557 /* Execute any appended to the statement with (...) */
1558 } else if ((*cmdList
)->bracketDepth
> myDepth
) {
1560 *cmdList
= WCMD_process_commands(*cmdList
, TRUE
, FALSE
);
1562 WINE_TRACE("Skipping command %p due to stack depth\n", *cmdList
);
1564 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1566 /* End of the command - does 'ELSE ' follow as the next command? */
1568 if (isIF
&& WCMD_keyword_ws_found(L
"else", (*cmdList
)->command
)) {
1569 /* Swap between if and else processing */
1570 processThese
= !executecmds
;
1572 /* Process the ELSE part */
1574 const int keyw_len
= lstrlenW(L
"else") + 1;
1575 WCHAR
*cmd
= ((*cmdList
)->command
) + keyw_len
;
1577 /* Skip leading whitespace between condition and the command */
1578 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
1580 WCMD_execute (cmd
, (*cmdList
)->redirects
, cmdList
, FALSE
);
1583 /* Loop skipping all commands until we get back to the current
1584 depth, including skipping commands and their subsequent
1585 pipes (eg cmd | prog) */
1587 *cmdList
= (*cmdList
)->nextcommand
;
1588 } while (*cmdList
&&
1589 ((*cmdList
)->bracketDepth
> myDepth
||
1590 (*cmdList
)->prevDelim
));
1592 /* After the else is complete, we need to now process subsequent commands */
1593 processThese
= TRUE
;
1595 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1597 /* If we were in an IF statement and we didn't find an else and yet we get back to
1598 the same bracket depth as the IF, then the IF statement is over. This is required
1599 to handle nested ifs properly */
1600 } else if (isIF
&& (*cmdList
)->bracketDepth
== myDepth
) {
1601 if (WCMD_keyword_ws_found(L
"do", (*cmdList
)->command
)) {
1602 WINE_TRACE("Still inside FOR-loop, not an end of IF statement\n");
1603 *cmdList
= (*cmdList
)->nextcommand
;
1605 WINE_TRACE("Found end of this nested IF statement, ending this if\n");
1608 } else if (!processThese
) {
1609 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1610 WINE_TRACE("Skipping this command, as in not process mode (next = %p)\n", *cmdList
);
1612 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
1621 /*****************************************************************************
1622 * WCMD_parse_forf_options
1624 * Parses the for /f 'options', extracting the values and validating the
1625 * keywords. Note all keywords are optional.
1627 * options [I] The unparsed parameter string
1628 * eol [O] Set to the comment character (eol=x)
1629 * skip [O] Set to the number of lines to skip (skip=xx)
1630 * delims [O] Set to the token delimiters (delims=)
1631 * tokens [O] Set to the requested tokens, as provided (tokens=)
1632 * usebackq [O] Set to TRUE if usebackq found
1634 * Returns TRUE on success, FALSE on syntax error
1637 static BOOL
WCMD_parse_forf_options(WCHAR
*options
, WCHAR
*eol
, int *skip
,
1638 WCHAR
*delims
, WCHAR
*tokens
, BOOL
*usebackq
)
1641 WCHAR
*pos
= options
;
1642 int len
= lstrlenW(pos
);
1643 const int eol_len
= lstrlenW(L
"eol=");
1644 const int skip_len
= lstrlenW(L
"skip=");
1645 const int tokens_len
= lstrlenW(L
"tokens=");
1646 const int delims_len
= lstrlenW(L
"delims=");
1647 const int usebackq_len
= lstrlenW(L
"usebackq");
1649 /* Initialize to defaults */
1650 lstrcpyW(delims
, L
" \t");
1651 lstrcpyW(tokens
, L
"1");
1656 /* Strip (optional) leading and trailing quotes */
1657 if ((*pos
== '"') && (pos
[len
-1] == '"')) {
1662 /* Process each keyword */
1663 while (pos
&& *pos
) {
1664 if (*pos
== ' ' || *pos
== '\t') {
1667 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1668 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1669 pos
, eol_len
, L
"eol=", eol_len
) == CSTR_EQUAL
) {
1670 *eol
= *(pos
+ eol_len
);
1671 pos
= pos
+ eol_len
+ 1;
1672 WINE_TRACE("Found eol as %c(%x)\n", *eol
, *eol
);
1674 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1675 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1676 pos
, skip_len
, L
"skip=", skip_len
) == CSTR_EQUAL
) {
1677 WCHAR
*nextchar
= NULL
;
1678 pos
= pos
+ skip_len
;
1679 *skip
= wcstoul(pos
, &nextchar
, 0);
1680 WINE_TRACE("Found skip as %d lines\n", *skip
);
1683 /* Save if usebackq semantics are in effect */
1684 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
, pos
,
1685 usebackq_len
, L
"usebackq", usebackq_len
) == CSTR_EQUAL
) {
1687 pos
= pos
+ usebackq_len
;
1688 WINE_TRACE("Found usebackq\n");
1690 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1691 if you finish the optionsroot string with delims= otherwise the space is
1692 just a token delimiter! */
1693 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1694 pos
, delims_len
, L
"delims=", delims_len
) == CSTR_EQUAL
) {
1697 pos
= pos
+ delims_len
;
1698 while (*pos
&& *pos
!= ' ') {
1702 if (*pos
==' ' && *(pos
+1)==0) delims
[i
++] = *pos
;
1703 delims
[i
++] = 0; /* Null terminate the delims */
1704 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims
));
1706 /* Save the tokens being requested */
1707 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1708 pos
, tokens_len
, L
"tokens=", tokens_len
) == CSTR_EQUAL
) {
1711 pos
= pos
+ tokens_len
;
1712 while (*pos
&& *pos
!= ' ') {
1716 tokens
[i
++] = 0; /* Null terminate the tokens */
1717 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens
));
1720 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos
));
1727 /*****************************************************************************
1728 * WCMD_add_dirstowalk
1730 * When recursing through directories (for /r), we need to add to the list of
1731 * directories still to walk, any subdirectories of the one we are processing.
1734 * options [I] The remaining list of directories still to process
1736 * Note this routine inserts the subdirectories found between the entry being
1737 * processed, and any other directory still to be processed, mimicking what
1740 static void WCMD_add_dirstowalk(DIRECTORY_STACK
*dirsToWalk
) {
1741 DIRECTORY_STACK
*remainingDirs
= dirsToWalk
;
1742 WCHAR fullitem
[MAX_PATH
];
1743 WIN32_FIND_DATAW fd
;
1746 /* Build a generic search and add all directories on the list of directories
1748 lstrcpyW(fullitem
, dirsToWalk
->dirName
);
1749 lstrcatW(fullitem
, L
"\\*");
1750 hff
= FindFirstFileW(fullitem
, &fd
);
1751 if (hff
!= INVALID_HANDLE_VALUE
) {
1753 WINE_TRACE("Looking for subdirectories\n");
1754 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
1755 (lstrcmpW(fd
.cFileName
, L
"..") != 0) && (lstrcmpW(fd
.cFileName
, L
".") != 0))
1757 /* Allocate memory, add to list */
1758 DIRECTORY_STACK
*toWalk
= heap_xalloc(sizeof(DIRECTORY_STACK
));
1759 WINE_TRACE("(%p->%p)\n", remainingDirs
, remainingDirs
->next
);
1760 toWalk
->next
= remainingDirs
->next
;
1761 remainingDirs
->next
= toWalk
;
1762 remainingDirs
= toWalk
;
1763 toWalk
->dirName
= heap_xalloc(sizeof(WCHAR
) * (lstrlenW(dirsToWalk
->dirName
) + 2 + lstrlenW(fd
.cFileName
)));
1764 lstrcpyW(toWalk
->dirName
, dirsToWalk
->dirName
);
1765 lstrcatW(toWalk
->dirName
, L
"\\");
1766 lstrcatW(toWalk
->dirName
, fd
.cFileName
);
1767 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk
->dirName
),
1768 toWalk
, toWalk
->next
);
1770 } while (FindNextFileW(hff
, &fd
) != 0);
1771 WINE_TRACE("Finished adding all subdirectories\n");
1776 /**************************************************************************
1777 * WCMD_for_nexttoken
1779 * Parse the token= line, identifying the next highest number not processed
1780 * so far. Count how many tokens are referred (including duplicates) and
1781 * optionally return that, plus optionally indicate if the tokens= line
1785 * lasttoken [I] - Identifies the token index of the last one
1786 * returned so far (-1 used for first loop)
1787 * tokenstr [I] - The specified tokens= line
1788 * firstCmd [O] - Optionally indicate how many tokens are listed
1789 * doAll [O] - Optionally indicate if line ends with *
1790 * duplicates [O] - Optionally indicate if there is any evidence of
1791 * overlaying tokens in the string
1792 * Note the caller should keep a running track of duplicates as the tokens
1793 * are recursively passed. If any have duplicates, then the * token should
1796 static int WCMD_for_nexttoken(int lasttoken
, WCHAR
*tokenstr
,
1797 int *totalfound
, BOOL
*doall
,
1800 WCHAR
*pos
= tokenstr
;
1803 if (totalfound
) *totalfound
= 0;
1804 if (doall
) *doall
= FALSE
;
1805 if (duplicates
) *duplicates
= FALSE
;
1807 WINE_TRACE("Find next token after %d in %s\n", lasttoken
,
1808 wine_dbgstr_w(tokenstr
));
1810 /* Loop through the token string, parsing it. Valid syntax is:
1811 token=m or x-y with comma delimiter and optionally * to finish*/
1813 int nextnumber1
, nextnumber2
= -1;
1816 /* Remember if the next character is a star, it indicates a need to
1817 show all remaining tokens and should be the last character */
1819 if (doall
) *doall
= TRUE
;
1820 if (totalfound
) (*totalfound
)++;
1821 /* If we have not found a next token to return, then indicate
1822 time to process the star */
1823 if (nexttoken
== -1) {
1824 if (lasttoken
== -1) {
1825 /* Special case the syntax of tokens=* which just means get whole line */
1828 nexttoken
= lasttoken
;
1834 /* Get the next number */
1835 nextnumber1
= wcstoul(pos
, &nextchar
, 10);
1837 /* If it is followed by a minus, it's a range, so get the next one as well */
1838 if (*nextchar
== '-') {
1839 nextnumber2
= wcstoul(nextchar
+1, &nextchar
, 10);
1841 /* We want to return the lowest number that is higher than lasttoken
1842 but only if range is positive */
1843 if (nextnumber2
>= nextnumber1
&&
1844 lasttoken
< nextnumber2
) {
1847 if (nexttoken
== -1) {
1848 nextvalue
= max(nextnumber1
, (lasttoken
+1));
1850 nextvalue
= min(nexttoken
, max(nextnumber1
, (lasttoken
+1)));
1853 /* Flag if duplicates identified */
1854 if (nexttoken
== nextvalue
&& duplicates
) *duplicates
= TRUE
;
1856 nexttoken
= nextvalue
;
1859 /* Update the running total for the whole range */
1860 if (nextnumber2
>= nextnumber1
&& totalfound
) {
1861 *totalfound
= *totalfound
+ 1 + (nextnumber2
- nextnumber1
);
1865 } else if (pos
!= nextchar
) {
1866 if (totalfound
) (*totalfound
)++;
1868 /* See if the number found is one we have already seen */
1869 if (nextnumber1
== nexttoken
&& duplicates
) *duplicates
= TRUE
;
1871 /* We want to return the lowest number that is higher than lasttoken */
1872 if (lasttoken
< nextnumber1
&&
1873 ((nexttoken
== -1) || (nextnumber1
< nexttoken
))) {
1874 nexttoken
= nextnumber1
;
1879 /* Step on to the next character, usually over comma */
1886 if (nexttoken
== -1) {
1887 WINE_TRACE("No next token found, previous was %d\n", lasttoken
);
1888 nexttoken
= lasttoken
;
1889 } else if (nexttoken
==lasttoken
&& doall
&& *doall
) {
1890 WINE_TRACE("Request for all remaining tokens now\n");
1892 WINE_TRACE("Found next token after %d was %d\n", lasttoken
, nexttoken
);
1894 if (totalfound
) WINE_TRACE("Found total tokens to be %d\n", *totalfound
);
1895 if (duplicates
&& *duplicates
) WINE_TRACE("Duplicate numbers found\n");
1899 /**************************************************************************
1902 * When parsing file or string contents (for /f), once the string to parse
1903 * has been identified, handle the various options and call the do part
1907 * cmdStart [I] - Identifies the list of commands making up the
1908 * for loop body (especially if brackets in use)
1909 * firstCmd [I] - The textual start of the command after the DO
1910 * which is within the first item of cmdStart
1911 * cmdEnd [O] - Identifies where to continue after the DO
1912 * variable [I] - The variable identified on the for line
1913 * buffer [I] - The string to parse
1914 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1915 * forf_skip [I/O] - How many lines to skip first
1916 * forf_eol [I] - The 'end of line' (comment) character
1917 * forf_delims [I] - The delimiters to use when breaking the string apart
1918 * forf_tokens [I] - The tokens to use when breaking the string apart
1920 static void WCMD_parse_line(CMD_LIST
*cmdStart
,
1921 const WCHAR
*firstCmd
,
1923 const WCHAR variable
,
1929 WCHAR
*forf_tokens
) {
1932 FOR_CONTEXT oldcontext
;
1933 int varidx
, varoffset
;
1934 int nexttoken
, lasttoken
= -1;
1935 BOOL starfound
= FALSE
;
1936 BOOL thisduplicate
= FALSE
;
1937 BOOL anyduplicates
= FALSE
;
1939 static WCHAR emptyW
[] = L
"";
1941 /* Skip lines if requested */
1947 /* Save away any existing for variable context (e.g. nested for loops) */
1948 oldcontext
= forloopcontext
;
1950 /* Extract the parameters based on the tokens= value (There will always
1951 be some value, as if it is not supplied, it defaults to tokens=1).
1953 Count how many tokens are named in the line, identify the lowest
1954 Empty (set to null terminated string) that number of named variables
1955 While lasttoken != nextlowest
1956 %letter = parameter number 'nextlowest'
1957 letter++ (if >26 or >52 abort)
1958 Go through token= string finding next lowest number
1959 If token ends in * set %letter = raw position of token(nextnumber+1)
1962 nexttoken
= WCMD_for_nexttoken(lasttoken
, forf_tokens
, &totalfound
,
1963 &starfound
, &thisduplicate
);
1964 varidx
= FOR_VAR_IDX(variable
);
1966 /* Empty out variables */
1968 varidx
>= 0 && varoffset
<totalfound
&& (((varidx
%26) + varoffset
) < 26);
1970 forloopcontext
.variable
[varidx
+ varoffset
] = emptyW
;
1973 /* Loop extracting the tokens
1974 Note: nexttoken of 0 means there were no tokens requested, to handle
1975 the special case of tokens=* */
1977 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer
));
1978 while (varidx
>= 0 && (nexttoken
> 0 && (nexttoken
> lasttoken
))) {
1979 anyduplicates
|= thisduplicate
;
1981 /* Extract the token number requested and set into the next variable context */
1982 parm
= WCMD_parameter_with_delims(buffer
, (nexttoken
-1), NULL
, TRUE
, FALSE
, forf_delims
);
1983 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken
,
1984 varidx
+ varoffset
, wine_dbgstr_w(parm
));
1986 if (parm
) forloopcontext
.variable
[varidx
+ varoffset
] = heap_strdupW(parm
);
1988 if (((varidx
%26)+varoffset
) >= 26) break;
1991 /* Find the next token */
1992 lasttoken
= nexttoken
;
1993 nexttoken
= WCMD_for_nexttoken(lasttoken
, forf_tokens
, NULL
,
1994 &starfound
, &thisduplicate
);
1997 /* If all the rest of the tokens were requested, and there is still space in
1998 the variable range, write them now */
1999 if (!anyduplicates
&& starfound
&& varidx
>= 0 && (((varidx
%26) + varoffset
) < 26)) {
2001 WCMD_parameter_with_delims(buffer
, (nexttoken
-1), &parm
, FALSE
, FALSE
, forf_delims
);
2002 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2003 varidx
+ varoffset
, wine_dbgstr_w(parm
));
2004 if (parm
) forloopcontext
.variable
[varidx
+ varoffset
] = heap_strdupW(parm
);
2007 /* Execute the body of the foor loop with these values */
2008 if (forloopcontext
.variable
[varidx
] && forloopcontext
.variable
[varidx
][0] != forf_eol
) {
2009 CMD_LIST
*thisCmdStart
= cmdStart
;
2011 WCMD_part_execute(&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2012 *cmdEnd
= thisCmdStart
;
2015 /* Free the duplicated strings, and restore the context */
2018 for (i
=varidx
; i
<MAX_FOR_VARIABLES
; i
++) {
2019 if ((forloopcontext
.variable
[i
] != oldcontext
.variable
[i
]) &&
2020 (forloopcontext
.variable
[i
] != emptyW
)) {
2021 heap_free(forloopcontext
.variable
[i
]);
2026 /* Restore the original for variable contextx */
2027 forloopcontext
= oldcontext
;
2030 /**************************************************************************
2031 * WCMD_forf_getinputhandle
2033 * Return a file handle which can be used for reading the input lines,
2034 * either to a specific file (which may be quote delimited as we have to
2035 * read the parameters in raw mode) or to a command which we need to
2036 * execute. The command being executed runs in its own shell and stores
2037 * its data in a temporary file.
2040 * usebackq [I] - Indicates whether usebackq is in effect or not
2041 * itemStr [I] - The item to be handled, either a filename or
2042 * whole command string to execute
2043 * iscmd [I] - Identifies whether this is a command or not
2045 * Returns a file handle which can be used to read the input lines from.
2047 static HANDLE
WCMD_forf_getinputhandle(BOOL usebackq
, WCHAR
*itemstr
, BOOL iscmd
) {
2048 WCHAR temp_str
[MAX_PATH
];
2049 WCHAR temp_file
[MAX_PATH
];
2050 WCHAR temp_cmd
[MAXSTRING
];
2051 WCHAR
*trimmed
= NULL
;
2052 HANDLE hinput
= INVALID_HANDLE_VALUE
;
2054 /* Remove leading and trailing character (but there may be trailing whitespace too) */
2055 if ((iscmd
&& (itemstr
[0] == '`' && usebackq
)) ||
2056 (iscmd
&& (itemstr
[0] == '\'' && !usebackq
)) ||
2057 (!iscmd
&& (itemstr
[0] == '"' && usebackq
)))
2059 trimmed
= WCMD_strtrim(itemstr
);
2063 itemstr
[lstrlenW(itemstr
)-1] = 0x00;
2068 /* Get temp filename */
2069 GetTempPathW(ARRAY_SIZE(temp_str
), temp_str
);
2070 GetTempFileNameW(temp_str
, L
"CMD", 0, temp_file
);
2072 /* Redirect output to the temporary file */
2073 wsprintfW(temp_str
, L
">%s", temp_file
);
2074 wsprintfW(temp_cmd
, L
"CMD.EXE /C %s", itemstr
);
2075 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2076 wine_dbgstr_w(temp_cmd
), wine_dbgstr_w(temp_str
));
2077 WCMD_execute (temp_cmd
, temp_str
, NULL
, FALSE
);
2079 /* Open the file, read line by line and process */
2080 hinput
= CreateFileW(temp_file
, GENERIC_READ
, FILE_SHARE_READ
,
2081 NULL
, OPEN_EXISTING
, FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
2084 /* Open the file, read line by line and process */
2085 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr
));
2086 hinput
= CreateFileW(itemstr
, GENERIC_READ
, FILE_SHARE_READ
,
2087 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
2093 /**************************************************************************
2096 * Batch file loop processing.
2098 * On entry: cmdList contains the syntax up to the set
2099 * next cmdList and all in that bracket contain the set data
2100 * next cmdlist contains the DO cmd
2101 * following that is either brackets or && entries (as per if)
2105 void WCMD_for (WCHAR
*p
, CMD_LIST
**cmdList
) {
2107 WIN32_FIND_DATAW fd
;
2110 const int in_len
= lstrlenW(L
"in");
2111 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
2114 WCHAR
*oldvariablevalue
;
2117 WCHAR optionsRoot
[MAX_PATH
];
2118 DIRECTORY_STACK
*dirsToWalk
= NULL
;
2119 BOOL expandDirs
= FALSE
;
2120 BOOL useNumbers
= FALSE
;
2121 BOOL doFileset
= FALSE
;
2122 BOOL doRecurse
= FALSE
;
2123 BOOL doExecuted
= FALSE
; /* Has the 'do' part been executed */
2124 LONG numbers
[3] = {0,0,0}; /* Defaults to 0 in native */
2126 CMD_LIST
*thisCmdStart
;
2127 int parameterNo
= 0;
2130 WCHAR forf_delims
[256];
2131 WCHAR forf_tokens
[MAXSTRING
];
2132 BOOL forf_usebackq
= FALSE
;
2134 /* Handle optional qualifiers (multiple are allowed) */
2135 WCHAR
*thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2138 while (thisArg
&& *thisArg
== '/') {
2139 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg
));
2141 switch (towupper(*thisArg
)) {
2142 case 'D': expandDirs
= TRUE
; break;
2143 case 'L': useNumbers
= TRUE
; break;
2145 /* Recursive is special case - /R can have an optional path following it */
2146 /* filenamesets are another special case - /F can have an optional options following it */
2150 /* When recursing directories, use current directory as the starting point unless
2151 subsequently overridden */
2152 doRecurse
= (towupper(*thisArg
) == 'R');
2153 if (doRecurse
) GetCurrentDirectoryW(ARRAY_SIZE(optionsRoot
), optionsRoot
);
2155 doFileset
= (towupper(*thisArg
) == 'F');
2157 /* Retrieve next parameter to see if is root/options (raw form required
2158 with for /f, or unquoted in for /r) */
2159 thisArg
= WCMD_parameter(p
, parameterNo
, NULL
, doFileset
, FALSE
);
2161 /* Next parm is either qualifier, path/options or variable -
2162 only care about it if it is the path/options */
2163 if (thisArg
&& *thisArg
!= '/' && *thisArg
!= '%') {
2165 lstrcpyW(optionsRoot
, thisArg
);
2170 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg
);
2173 /* Step to next token */
2174 thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2177 /* Ensure line continues with variable */
2178 if (*thisArg
!= '%') {
2179 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2183 /* With for /f parse the options if provided */
2185 if (!WCMD_parse_forf_options(optionsRoot
, &forf_eol
, &forf_skip
,
2186 forf_delims
, forf_tokens
, &forf_usebackq
))
2188 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2192 /* Set up the list of directories to recurse if we are going to */
2193 } else if (doRecurse
) {
2194 /* Allocate memory, add to list */
2195 dirsToWalk
= heap_xalloc(sizeof(DIRECTORY_STACK
));
2196 dirsToWalk
->next
= NULL
;
2197 dirsToWalk
->dirName
= heap_strdupW(optionsRoot
);
2198 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk
->dirName
));
2201 /* Variable should follow */
2202 lstrcpyW(variable
, thisArg
);
2203 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable
));
2204 varidx
= FOR_VAR_IDX(variable
[1]);
2206 /* Ensure line continues with IN */
2207 thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2209 || !(CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2210 thisArg
, in_len
, L
"in", in_len
) == CSTR_EQUAL
)) {
2211 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2215 /* Save away where the set of data starts and the variable */
2216 thisDepth
= (*cmdList
)->bracketDepth
;
2217 *cmdList
= (*cmdList
)->nextcommand
;
2218 setStart
= (*cmdList
);
2220 /* Skip until the close bracket */
2221 WINE_TRACE("Searching %p as the set\n", *cmdList
);
2223 (*cmdList
)->command
!= NULL
&&
2224 (*cmdList
)->bracketDepth
> thisDepth
) {
2225 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList
);
2226 *cmdList
= (*cmdList
)->nextcommand
;
2229 /* Skip the close bracket, if there is one */
2230 if (*cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
2232 /* Syntax error if missing close bracket, or nothing following it
2233 and once we have the complete set, we expect a DO */
2234 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList
);
2235 if ((*cmdList
== NULL
) || !WCMD_keyword_ws_found(L
"do", (*cmdList
)->command
)) {
2236 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2242 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2243 mode, or once for the rest of the time. */
2246 /* Save away the starting position for the commands (and offset for the
2248 cmdStart
= *cmdList
;
2249 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
2252 /* If we are recursing directories (ie /R), add all sub directories now, then
2253 prefix the root when searching for the item */
2254 if (dirsToWalk
) WCMD_add_dirstowalk(dirsToWalk
);
2257 /* Loop through all set entries */
2259 thisSet
->command
!= NULL
&&
2260 thisSet
->bracketDepth
>= thisDepth
) {
2262 /* Loop through all entries on the same line */
2265 WCHAR buffer
[MAXSTRING
];
2267 WINE_TRACE("Processing for set %p\n", thisSet
);
2269 while (*(staticitem
= WCMD_parameter (thisSet
->command
, i
, &itemStart
, TRUE
, FALSE
))) {
2272 * If the parameter within the set has a wildcard then search for matching files
2273 * otherwise do a literal substitution.
2276 /* Take a copy of the item returned from WCMD_parameter as it is held in a
2277 static buffer which can be overwritten during parsing of the for body */
2278 WCHAR item
[MAXSTRING
];
2279 lstrcpyW(item
, staticitem
);
2281 thisCmdStart
= cmdStart
;
2284 WINE_TRACE("Processing for item %d '%s'\n", itemNum
, wine_dbgstr_w(item
));
2286 if (!useNumbers
&& !doFileset
) {
2287 WCHAR fullitem
[MAX_PATH
];
2290 /* Now build the item to use / search for in the specified directory,
2291 as it is fully qualified in the /R case */
2293 lstrcpyW(fullitem
, dirsToWalk
->dirName
);
2294 lstrcatW(fullitem
, L
"\\");
2295 lstrcatW(fullitem
, item
);
2297 WCHAR
*prefix
= wcsrchr(item
, '\\');
2298 if (prefix
) prefixlen
= (prefix
- item
) + 1;
2299 lstrcpyW(fullitem
, item
);
2302 if (wcspbrk(fullitem
, L
"*?")) {
2303 hff
= FindFirstFileW(fullitem
, &fd
);
2304 if (hff
!= INVALID_HANDLE_VALUE
) {
2306 BOOL isDirectory
= FALSE
;
2308 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) isDirectory
= TRUE
;
2310 /* Handle as files or dirs appropriately, but ignore . and .. */
2311 if (isDirectory
== expandDirs
&&
2312 (lstrcmpW(fd
.cFileName
, L
"..") != 0) && (lstrcmpW(fd
.cFileName
, L
".") != 0))
2314 thisCmdStart
= cmdStart
;
2315 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd
.cFileName
));
2318 lstrcpyW(fullitem
, dirsToWalk
->dirName
);
2319 lstrcatW(fullitem
, L
"\\");
2320 lstrcatW(fullitem
, fd
.cFileName
);
2322 if (prefixlen
) lstrcpynW(fullitem
, item
, prefixlen
+ 1);
2323 fullitem
[prefixlen
] = 0x00;
2324 lstrcatW(fullitem
, fd
.cFileName
);
2328 /* Save away any existing for variable context (e.g. nested for loops)
2329 and restore it after executing the body of this for loop */
2331 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2332 forloopcontext
.variable
[varidx
] = fullitem
;
2334 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2335 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2337 cmdEnd
= thisCmdStart
;
2339 } while (FindNextFileW(hff
, &fd
) != 0);
2345 /* Save away any existing for variable context (e.g. nested for loops)
2346 and restore it after executing the body of this for loop */
2348 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2349 forloopcontext
.variable
[varidx
] = fullitem
;
2351 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2352 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2354 cmdEnd
= thisCmdStart
;
2357 } else if (useNumbers
) {
2358 /* Convert the first 3 numbers to signed longs and save */
2359 if (itemNum
<=3) numbers
[itemNum
-1] = wcstol(item
, NULL
, 10);
2360 /* else ignore them! */
2362 /* Filesets - either a list of files, or a command to run and parse the output */
2363 } else if (doFileset
&& ((!forf_usebackq
&& *itemStart
!= '"') ||
2364 (forf_usebackq
&& *itemStart
!= '\''))) {
2369 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum
,
2370 wine_dbgstr_w(item
));
2372 /* If backquote or single quote, we need to launch that command
2373 and parse the results - use a temporary file */
2374 if ((forf_usebackq
&& *itemStart
== '`') ||
2375 (!forf_usebackq
&& *itemStart
== '\'')) {
2377 /* Use itemstart because the command is the whole set, not just the first token */
2378 itemparm
= itemStart
;
2381 /* Use item because the file to process is just the first item in the set */
2384 input
= WCMD_forf_getinputhandle(forf_usebackq
, itemparm
, (itemparm
==itemStart
));
2386 /* Process the input file */
2387 if (input
== INVALID_HANDLE_VALUE
) {
2388 WCMD_print_error ();
2389 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), item
);
2391 return; /* FOR loop aborts at first failure here */
2395 /* Read line by line until end of file */
2396 while (WCMD_fgets(buffer
, ARRAY_SIZE(buffer
), input
)) {
2397 WCMD_parse_line(cmdStart
, firstCmd
, &cmdEnd
, variable
[1], buffer
, &doExecuted
,
2398 &forf_skip
, forf_eol
, forf_delims
, forf_tokens
);
2401 CloseHandle (input
);
2404 /* When we have processed the item as a whole command, abort future set processing */
2405 if (itemparm
==itemStart
) {
2410 /* Filesets - A string literal */
2411 } else if (doFileset
&& ((!forf_usebackq
&& *itemStart
== '"') ||
2412 (forf_usebackq
&& *itemStart
== '\''))) {
2414 /* Remove leading and trailing character, ready to parse with delims= delimiters
2415 Note that the last quote is removed from the set and the string terminates
2416 there to mimic windows */
2417 WCHAR
*strend
= wcsrchr(itemStart
, forf_usebackq
?'\'':'"');
2423 /* Copy the item away from the global buffer used by WCMD_parameter */
2424 lstrcpyW(buffer
, itemStart
);
2425 WCMD_parse_line(cmdStart
, firstCmd
, &cmdEnd
, variable
[1], buffer
, &doExecuted
,
2426 &forf_skip
, forf_eol
, forf_delims
, forf_tokens
);
2428 /* Only one string can be supplied in the whole set, abort future set processing */
2433 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
2437 /* Move onto the next set line */
2438 if (thisSet
) thisSet
= thisSet
->nextcommand
;
2441 /* If /L is provided, now run the for loop */
2445 WINE_TRACE("FOR /L provided range from %ld to %ld step %ld\n",
2446 numbers
[0], numbers
[2], numbers
[1]);
2448 (numbers
[1]<0)? i
>=numbers
[2] : i
<=numbers
[2];
2451 swprintf(thisNum
, ARRAY_SIZE(thisNum
), L
"%d", i
);
2452 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum
));
2454 thisCmdStart
= cmdStart
;
2457 /* Save away any existing for variable context (e.g. nested for loops)
2458 and restore it after executing the body of this for loop */
2460 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2461 forloopcontext
.variable
[varidx
] = thisNum
;
2463 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2464 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2466 cmdEnd
= thisCmdStart
;
2469 /* If we are walking directories, move on to any which remain */
2470 if (dirsToWalk
!= NULL
) {
2471 DIRECTORY_STACK
*nextDir
= dirsToWalk
->next
;
2472 heap_free(dirsToWalk
->dirName
);
2473 heap_free(dirsToWalk
);
2474 dirsToWalk
= nextDir
;
2475 if (dirsToWalk
) WINE_TRACE("Moving to next directory to iterate: %s\n",
2476 wine_dbgstr_w(dirsToWalk
->dirName
));
2477 else WINE_TRACE("Finished all directories.\n");
2480 } while (dirsToWalk
!= NULL
);
2482 /* Now skip over the do part if we did not perform the for loop so far.
2483 We store in cmdEnd the next command after the do block, but we only
2484 know this if something was run. If it has not been, we need to calculate
2487 thisCmdStart
= cmdStart
;
2488 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2489 WCMD_part_execute(&thisCmdStart
, firstCmd
, FALSE
, FALSE
);
2490 cmdEnd
= thisCmdStart
;
2493 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2494 all processing, OR it should be pointing to the end of && processing OR
2495 it should be pointing at the NULL end of bracket for the DO. The return
2496 value needs to be the NEXT command to execute, which it either is, or
2497 we need to step over the closing bracket */
2499 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
2502 /**************************************************************************
2505 * Simple on-line help. Help text is stored in the resource file.
2508 void WCMD_give_help (const WCHAR
*args
)
2512 args
= WCMD_skip_leading_spaces((WCHAR
*) args
);
2514 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
2517 /* Display help message for builtin commands */
2518 for (i
=0; i
<=WCMD_EXIT
; i
++) {
2519 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2520 args
, -1, inbuilt
[i
], -1) == CSTR_EQUAL
) {
2521 WCMD_output_asis (WCMD_LoadMessage(i
));
2525 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2526 for (i
= 0; i
<= ARRAY_SIZE(externals
); i
++) {
2527 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2528 args
, -1, externals
[i
], -1) == CSTR_EQUAL
) {
2530 lstrcpyW(cmd
, args
);
2531 lstrcatW(cmd
, L
" /?");
2532 WCMD_run_program(cmd
, FALSE
);
2536 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), args
);
2541 /****************************************************************************
2544 * Batch file jump instruction. Not the most efficient algorithm ;-)
2545 * Prints error message if the specified label cannot be found - the file pointer is
2546 * then at EOF, effectively stopping the batch file.
2547 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2550 void WCMD_goto (CMD_LIST
**cmdList
) {
2552 WCHAR string
[MAX_PATH
];
2553 WCHAR
*labelend
= NULL
;
2554 const WCHAR labelEndsW
[] = L
"><|& :\t";
2556 /* Do not process any more parts of a processed multipart or multilines command */
2557 if (cmdList
) *cmdList
= NULL
;
2559 if (context
!= NULL
) {
2560 WCHAR
*paramStart
= param1
, *str
;
2562 if (param1
[0] == 0x00) {
2563 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2567 /* Handle special :EOF label */
2568 if (lstrcmpiW(L
":eof", param1
) == 0) {
2569 context
-> skip_rest
= TRUE
;
2573 /* Support goto :label as well as goto label plus remove trailing chars */
2574 if (*paramStart
== ':') paramStart
++;
2575 labelend
= wcspbrk(paramStart
, labelEndsW
);
2576 if (labelend
) *labelend
= 0x00;
2577 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart
));
2579 /* Loop through potentially twice - once from current file position
2580 through to the end, and second time from start to current file
2584 LARGE_INTEGER startli
;
2585 for (loop
=0; loop
<2; loop
++) {
2587 /* On first loop, save the file size */
2588 startli
.QuadPart
= 0;
2589 startli
.u
.LowPart
= SetFilePointer(context
-> h
, startli
.u
.LowPart
,
2590 &startli
.u
.HighPart
, FILE_CURRENT
);
2592 /* On second loop, start at the beginning of the file */
2593 WINE_TRACE("Label not found, trying from beginning of file\n");
2594 if (loop
==1) SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
2597 while (WCMD_fgets (string
, ARRAY_SIZE(string
), context
-> h
)) {
2600 /* Ignore leading whitespace or no-echo character */
2601 while (*str
=='@' || iswspace (*str
)) str
++;
2603 /* If the first real character is a : then this is a label */
2607 /* Skip spaces between : and label */
2608 while (iswspace (*str
)) str
++;
2609 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str
));
2611 /* Label ends at whitespace or redirection characters */
2612 labelend
= wcspbrk(str
, labelEndsW
);
2613 if (labelend
) *labelend
= 0x00;
2614 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str
));
2616 if (lstrcmpiW (str
, paramStart
) == 0) return;
2619 /* See if we have gone beyond the end point if second time through */
2621 LARGE_INTEGER curli
;
2623 curli
.u
.LowPart
= SetFilePointer(context
-> h
, curli
.u
.LowPart
,
2624 &curli
.u
.HighPart
, FILE_CURRENT
);
2625 if (curli
.QuadPart
> startli
.QuadPart
) {
2626 WINE_TRACE("Reached wrap point, label not found\n");
2634 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET
));
2635 context
-> skip_rest
= TRUE
;
2640 /*****************************************************************************
2643 * Push a directory onto the stack
2646 void WCMD_pushd (const WCHAR
*args
)
2648 struct env_stack
*curdir
;
2651 if (wcschr(args
, '/') != NULL
) {
2652 SetLastError(ERROR_INVALID_PARAMETER
);
2657 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
2658 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
2659 if( !curdir
|| !thisdir
) {
2662 WINE_ERR ("out of memory\n");
2666 /* Change directory using CD code with /D parameter */
2667 lstrcpyW(quals
, L
"/D");
2668 GetCurrentDirectoryW (1024, thisdir
);
2670 WCMD_setshow_default(args
);
2676 curdir
-> next
= pushd_directories
;
2677 curdir
-> strings
= thisdir
;
2678 if (pushd_directories
== NULL
) {
2679 curdir
-> u
.stackdepth
= 1;
2681 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
2683 pushd_directories
= curdir
;
2688 /*****************************************************************************
2691 * Pop a directory from the stack
2694 void WCMD_popd (void) {
2695 struct env_stack
*temp
= pushd_directories
;
2697 if (!pushd_directories
)
2700 /* pop the old environment from the stack, and make it the current dir */
2701 pushd_directories
= temp
->next
;
2702 SetCurrentDirectoryW(temp
->strings
);
2703 LocalFree (temp
->strings
);
2707 /*******************************************************************
2708 * evaluate_if_comparison
2710 * Evaluates an "if" comparison operation
2713 * leftOperand [I] left operand, non NULL
2714 * operator [I] "if" binary comparison operator, non NULL
2715 * rightOperand [I] right operand, non NULL
2716 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2719 * Success: 1 if operator applied to the operands evaluates to TRUE
2720 * 0 if operator applied to the operands evaluates to FALSE
2721 * Failure: -1 if operator is not recognized
2723 static int evaluate_if_comparison(const WCHAR
*leftOperand
, const WCHAR
*operator,
2724 const WCHAR
*rightOperand
, int caseInsensitive
)
2726 WCHAR
*endptr_leftOp
, *endptr_rightOp
;
2727 long int leftOperand_int
, rightOperand_int
;
2730 /* == is a special case, as it always compares strings */
2731 if (!lstrcmpiW(operator, L
"=="))
2732 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) == 0
2733 : lstrcmpW (leftOperand
, rightOperand
) == 0;
2735 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2736 leftOperand_int
= wcstol(leftOperand
, &endptr_leftOp
, 0);
2737 rightOperand_int
= wcstol(rightOperand
, &endptr_rightOp
, 0);
2738 int_operands
= (!*endptr_leftOp
) && (!*endptr_rightOp
);
2740 /* Perform actual (integer or string) comparison */
2741 if (!lstrcmpiW(operator, L
"lss")) {
2743 return leftOperand_int
< rightOperand_int
;
2745 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) < 0
2746 : lstrcmpW (leftOperand
, rightOperand
) < 0;
2749 if (!lstrcmpiW(operator, L
"leq")) {
2751 return leftOperand_int
<= rightOperand_int
;
2753 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) <= 0
2754 : lstrcmpW (leftOperand
, rightOperand
) <= 0;
2757 if (!lstrcmpiW(operator, L
"equ")) {
2759 return leftOperand_int
== rightOperand_int
;
2761 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) == 0
2762 : lstrcmpW (leftOperand
, rightOperand
) == 0;
2765 if (!lstrcmpiW(operator, L
"neq")) {
2767 return leftOperand_int
!= rightOperand_int
;
2769 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) != 0
2770 : lstrcmpW (leftOperand
, rightOperand
) != 0;
2773 if (!lstrcmpiW(operator, L
"geq")) {
2775 return leftOperand_int
>= rightOperand_int
;
2777 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) >= 0
2778 : lstrcmpW (leftOperand
, rightOperand
) >= 0;
2781 if (!lstrcmpiW(operator, L
"gtr")) {
2783 return leftOperand_int
> rightOperand_int
;
2785 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) > 0
2786 : lstrcmpW (leftOperand
, rightOperand
) > 0;
2792 int evaluate_if_condition(WCHAR
*p
, WCHAR
**command
, int *test
, int *negate
)
2794 WCHAR condition
[MAX_PATH
];
2795 int caseInsensitive
= (wcsstr(quals
, L
"/I") != NULL
);
2797 *negate
= !lstrcmpiW(param1
,L
"not");
2798 lstrcpyW(condition
, (*negate
? param2
: param1
));
2799 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition
));
2801 if (!lstrcmpiW(condition
, L
"errorlevel")) {
2802 WCHAR
*param
= WCMD_parameter(p
, 1+(*negate
), NULL
, FALSE
, FALSE
);
2804 long int param_int
= wcstol(param
, &endptr
, 10);
2805 if (*endptr
) goto syntax_err
;
2806 *test
= ((long int)errorlevel
>= param_int
);
2807 WCMD_parameter(p
, 2+(*negate
), command
, FALSE
, FALSE
);
2809 else if (!lstrcmpiW(condition
, L
"exist")) {
2810 WIN32_FIND_DATAW fd
;
2812 WCHAR
*param
= WCMD_parameter(p
, 1+(*negate
), NULL
, FALSE
, FALSE
);
2813 int len
= lstrlenW(param
);
2815 if (!len
) goto syntax_err
;
2816 /* FindFirstFile does not like a directory path ending in '\', append a '.' */
2817 if (param
[len
-1] == '\\') lstrcatW(param
, L
".");
2819 hff
= FindFirstFileW(param
, &fd
);
2820 *test
= (hff
!= INVALID_HANDLE_VALUE
);
2821 if (*test
) FindClose(hff
);
2823 WCMD_parameter(p
, 2+(*negate
), command
, FALSE
, FALSE
);
2825 else if (!lstrcmpiW(condition
, L
"defined")) {
2826 *test
= (GetEnvironmentVariableW(WCMD_parameter(p
, 1+(*negate
), NULL
, FALSE
, FALSE
),
2828 WCMD_parameter(p
, 2+(*negate
), command
, FALSE
, FALSE
);
2830 else { /* comparison operation */
2831 WCHAR leftOperand
[MAXSTRING
], rightOperand
[MAXSTRING
], operator[MAXSTRING
];
2834 lstrcpyW(leftOperand
, WCMD_parameter(p
, (*negate
)+caseInsensitive
, ¶mStart
, TRUE
, FALSE
));
2838 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2839 p
= paramStart
+ lstrlenW(leftOperand
);
2840 while (*p
== ' ' || *p
== '\t')
2843 if (!wcsncmp(p
, L
"==", lstrlenW(L
"==")))
2844 lstrcpyW(operator, L
"==");
2846 lstrcpyW(operator, WCMD_parameter(p
, 0, ¶mStart
, FALSE
, FALSE
));
2847 if (!*operator) goto syntax_err
;
2849 p
+= lstrlenW(operator);
2851 lstrcpyW(rightOperand
, WCMD_parameter(p
, 0, ¶mStart
, TRUE
, FALSE
));
2855 *test
= evaluate_if_comparison(leftOperand
, operator, rightOperand
, caseInsensitive
);
2859 p
= paramStart
+ lstrlenW(rightOperand
);
2860 WCMD_parameter(p
, 0, command
, FALSE
, FALSE
);
2869 /****************************************************************************
2872 * Batch file conditional.
2874 * On entry, cmdlist will point to command containing the IF, and optionally
2875 * the first command to execute (if brackets not found)
2876 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2877 * If ('s were found, execute all within that bracket
2878 * Command may optionally be followed by an ELSE - need to skip instructions
2879 * in the else using the same logic
2881 * FIXME: Much more syntax checking needed!
2883 void WCMD_if (WCHAR
*p
, CMD_LIST
**cmdList
)
2885 int negate
; /* Negate condition */
2886 int test
; /* Condition evaluation result */
2889 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
2890 set in a call to WCMD_parse before */
2891 if (evaluate_if_condition(p
, &command
, &test
, &negate
) == -1)
2894 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n",
2895 wine_dbgstr_w(p
), wine_dbgstr_w(quals
), wine_dbgstr_w(param1
),
2896 wine_dbgstr_w(param2
), wine_dbgstr_w(command
));
2898 /* Process rest of IF statement which is on the same line
2899 Note: This may process all or some of the cmdList (eg a GOTO) */
2900 WCMD_part_execute(cmdList
, command
, TRUE
, (test
!= negate
));
2904 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
2907 /****************************************************************************
2910 * Move a file, directory tree or wildcarded set of files.
2913 void WCMD_move (void)
2916 WIN32_FIND_DATAW fd
;
2918 WCHAR input
[MAX_PATH
];
2919 WCHAR output
[MAX_PATH
];
2921 WCHAR dir
[MAX_PATH
];
2922 WCHAR fname
[MAX_PATH
];
2923 WCHAR ext
[MAX_PATH
];
2925 if (param1
[0] == 0x00) {
2926 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2930 /* If no destination supplied, assume current directory */
2931 if (param2
[0] == 0x00) {
2932 lstrcpyW(param2
, L
".");
2935 /* If 2nd parm is directory, then use original filename */
2936 /* Convert partial path to full path */
2937 if (!WCMD_get_fullpath(param1
, ARRAY_SIZE(input
), input
, NULL
) ||
2938 !WCMD_get_fullpath(param2
, ARRAY_SIZE(output
), output
, NULL
)) return;
2939 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
2940 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
2942 /* Split into components */
2943 _wsplitpath(input
, drive
, dir
, fname
, ext
);
2945 hff
= FindFirstFileW(input
, &fd
);
2946 if (hff
== INVALID_HANDLE_VALUE
)
2950 WCHAR dest
[MAX_PATH
];
2951 WCHAR src
[MAX_PATH
];
2956 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
2958 /* Build src & dest name */
2959 lstrcpyW(src
, drive
);
2962 /* See if dest is an existing directory */
2963 attribs
= GetFileAttributesW(output
);
2964 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
2965 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
2966 lstrcpyW(dest
, output
);
2967 lstrcatW(dest
, L
"\\");
2968 lstrcatW(dest
, fd
.cFileName
);
2970 lstrcpyW(dest
, output
);
2973 lstrcatW(src
, fd
.cFileName
);
2975 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
2976 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
2978 /* If destination exists, prompt unless /Y supplied */
2979 if (GetFileAttributesW(dest
) != INVALID_FILE_ATTRIBUTES
) {
2981 WCHAR copycmd
[MAXSTRING
];
2984 /* Default whether automatic overwriting is on. If we are interactive then
2985 we prompt by default, otherwise we overwrite by default
2986 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2987 if (wcsstr(quals
, L
"/-Y"))
2989 else if (wcsstr(quals
, L
"/Y"))
2992 /* By default, we will force the overwrite in batch mode and ask for
2993 * confirmation in interactive mode. */
2994 force
= !interactive
;
2995 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
2996 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
2997 * default behavior. */
2998 len
= GetEnvironmentVariableW(L
"COPYCMD", copycmd
, ARRAY_SIZE(copycmd
));
2999 if (len
&& len
< ARRAY_SIZE(copycmd
)) {
3000 if (!lstrcmpiW(copycmd
, L
"/Y"))
3002 else if (!lstrcmpiW(copycmd
, L
"/-Y"))
3007 /* Prompt if overwriting */
3011 /* Ask for confirmation */
3012 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
3013 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
3014 LocalFree(question
);
3018 flags
|= MOVEFILE_REPLACE_EXISTING
;
3022 status
= MoveFileExW(src
, dest
, flags
);
3028 WCMD_print_error ();
3031 } while (FindNextFileW(hff
, &fd
) != 0);
3036 /****************************************************************************
3039 * Suspend execution of a batch script until a key is typed
3042 void WCMD_pause (void)
3048 HANDLE hIn
= GetStdHandle(STD_INPUT_HANDLE
);
3050 have_console
= GetConsoleMode(hIn
, &oldmode
);
3052 SetConsoleMode(hIn
, 0);
3054 WCMD_output_asis(anykey
);
3055 WCMD_ReadFile(hIn
, &key
, 1, &count
);
3057 SetConsoleMode(hIn
, oldmode
);
3060 /****************************************************************************
3063 * Delete a directory.
3066 void WCMD_remove_dir (WCHAR
*args
) {
3069 int argsProcessed
= 0;
3072 /* Loop through all args */
3074 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
3075 if (argN
&& argN
[0] != '/') {
3076 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
3077 wine_dbgstr_w(quals
));
3080 /* If subdirectory search not supplied, just try to remove
3081 and report error if it fails (eg if it contains a file) */
3082 if (wcsstr(quals
, L
"/S") == NULL
) {
3083 if (!RemoveDirectoryW(thisArg
)) WCMD_print_error ();
3085 /* Otherwise use ShFileOp to recursively remove a directory */
3088 SHFILEOPSTRUCTW lpDir
;
3091 if (wcsstr(quals
, L
"/Q") == NULL
) {
3093 WCHAR question
[MAXSTRING
];
3095 /* Ask for confirmation */
3096 wsprintfW(question
, L
"%s ", thisArg
);
3097 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
3099 /* Abort if answer is 'N' */
3106 lpDir
.pFrom
= thisArg
;
3107 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
3108 lpDir
.wFunc
= FO_DELETE
;
3110 /* SHFileOperationW needs file list with a double null termination */
3111 thisArg
[lstrlenW(thisArg
) + 1] = 0x00;
3113 if (SHFileOperationW(&lpDir
)) WCMD_print_error ();
3118 /* Handle no valid args */
3119 if (argsProcessed
== 0) {
3120 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
3126 /****************************************************************************
3132 void WCMD_rename (void)
3136 WIN32_FIND_DATAW fd
;
3137 WCHAR input
[MAX_PATH
];
3138 WCHAR
*dotDst
= NULL
;
3140 WCHAR dir
[MAX_PATH
];
3141 WCHAR fname
[MAX_PATH
];
3142 WCHAR ext
[MAX_PATH
];
3146 /* Must be at least two args */
3147 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
3148 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
3153 /* Destination cannot contain a drive letter or directory separator */
3154 if ((wcschr(param2
,':') != NULL
) || (wcschr(param2
,'\\') != NULL
)) {
3155 SetLastError(ERROR_INVALID_PARAMETER
);
3161 /* Convert partial path to full path */
3162 if (!WCMD_get_fullpath(param1
, ARRAY_SIZE(input
), input
, NULL
)) return;
3163 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
3164 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
3165 dotDst
= wcschr(param2
, '.');
3167 /* Split into components */
3168 _wsplitpath(input
, drive
, dir
, fname
, ext
);
3170 hff
= FindFirstFileW(input
, &fd
);
3171 if (hff
== INVALID_HANDLE_VALUE
)
3175 WCHAR dest
[MAX_PATH
];
3176 WCHAR src
[MAX_PATH
];
3177 WCHAR
*dotSrc
= NULL
;
3180 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
3182 /* FIXME: If dest name or extension is *, replace with filename/ext
3183 part otherwise use supplied name. This supports:
3185 ren jim.* fred.* etc
3186 However, windows has a more complex algorithm supporting eg
3187 ?'s and *'s mid name */
3188 dotSrc
= wcschr(fd
.cFileName
, '.');
3190 /* Build src & dest name */
3191 lstrcpyW(src
, drive
);
3193 lstrcpyW(dest
, src
);
3194 dirLen
= lstrlenW(src
);
3195 lstrcatW(src
, fd
.cFileName
);
3198 if (param2
[0] == '*') {
3199 lstrcatW(dest
, fd
.cFileName
);
3200 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
3202 lstrcatW(dest
, param2
);
3203 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
3206 /* Build Extension */
3207 if (dotDst
&& (*(dotDst
+1)=='*')) {
3208 if (dotSrc
) lstrcatW(dest
, dotSrc
);
3209 } else if (dotDst
) {
3210 lstrcatW(dest
, dotDst
);
3213 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
3214 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
3216 status
= MoveFileW(src
, dest
);
3219 WCMD_print_error ();
3222 } while (FindNextFileW(hff
, &fd
) != 0);
3227 /*****************************************************************************
3230 * Make a copy of the environment.
3232 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
3242 len
+= (lstrlenW(&env
[len
]) + 1);
3244 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
3247 WINE_ERR("out of memory\n");
3250 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
3256 /*****************************************************************************
3259 * setlocal pushes the environment onto a stack
3260 * Save the environment as unicode so we don't screw anything up.
3262 void WCMD_setlocal (const WCHAR
*s
) {
3264 struct env_stack
*env_copy
;
3265 WCHAR cwd
[MAX_PATH
];
3268 /* setlocal does nothing outside of batch programs */
3269 if (!context
) return;
3271 /* DISABLEEXTENSIONS ignored */
3273 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3274 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3275 if (!wcsicmp(param1
, L
"ENABLEDELAYEDEXPANSION") || !wcsicmp(param2
, L
"ENABLEDELAYEDEXPANSION")) {
3277 } else if (!wcsicmp(param1
, L
"DISABLEDELAYEDEXPANSION") || !wcsicmp(param2
, L
"DISABLEDELAYEDEXPANSION")) {
3280 newdelay
= delayedsubst
;
3282 WINE_TRACE("Setting delayed expansion to %d\n", newdelay
);
3284 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
3287 WINE_ERR ("out of memory\n");
3291 env
= GetEnvironmentStringsW ();
3292 env_copy
->strings
= WCMD_dupenv (env
);
3293 if (env_copy
->strings
)
3295 env_copy
->batchhandle
= context
->h
;
3296 env_copy
->next
= saved_environment
;
3297 env_copy
->delayedsubst
= delayedsubst
;
3298 delayedsubst
= newdelay
;
3299 saved_environment
= env_copy
;
3301 /* Save the current drive letter */
3302 GetCurrentDirectoryW(MAX_PATH
, cwd
);
3303 env_copy
->u
.cwd
= cwd
[0];
3306 LocalFree (env_copy
);
3308 FreeEnvironmentStringsW (env
);
3312 /*****************************************************************************
3315 * endlocal pops the environment off a stack
3316 * Note: When searching for '=', search from WCHAR position 1, to handle
3317 * special internal environment variables =C:, =D: etc
3319 void WCMD_endlocal (void) {
3320 WCHAR
*env
, *old
, *p
;
3321 struct env_stack
*temp
;
3324 /* setlocal does nothing outside of batch programs */
3325 if (!context
) return;
3327 /* setlocal needs a saved environment from within the same context (batch
3328 program) as it was saved in */
3329 if (!saved_environment
|| saved_environment
->batchhandle
!= context
->h
)
3332 /* pop the old environment from the stack */
3333 temp
= saved_environment
;
3334 saved_environment
= temp
->next
;
3336 /* delete the current environment, totally */
3337 env
= GetEnvironmentStringsW ();
3338 old
= WCMD_dupenv (env
);
3341 n
= lstrlenW(&old
[len
]) + 1;
3342 p
= wcschr(&old
[len
] + 1, '=');
3346 SetEnvironmentVariableW (&old
[len
], NULL
);
3351 FreeEnvironmentStringsW (env
);
3353 /* restore old environment */
3354 env
= temp
->strings
;
3356 delayedsubst
= temp
->delayedsubst
;
3357 WINE_TRACE("Delayed expansion now %d\n", delayedsubst
);
3359 n
= lstrlenW(&env
[len
]) + 1;
3360 p
= wcschr(&env
[len
] + 1, '=');
3364 SetEnvironmentVariableW (&env
[len
], p
);
3369 /* Restore current drive letter */
3370 if (IsCharAlphaW(temp
->u
.cwd
)) {
3372 WCHAR cwd
[MAX_PATH
];
3374 wsprintfW(envvar
, L
"=%c:", temp
->u
.cwd
);
3375 if (GetEnvironmentVariableW(envvar
, cwd
, MAX_PATH
)) {
3376 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
3377 SetCurrentDirectoryW(cwd
);
3385 /*****************************************************************************
3386 * WCMD_setshow_default
3388 * Set/Show the current default directory
3391 void WCMD_setshow_default (const WCHAR
*args
) {
3397 WIN32_FIND_DATAW fd
;
3400 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args
));
3402 /* Skip /D and trailing whitespace if on the front of the command line */
3403 if (lstrlenW(args
) >= 2 &&
3404 CompareStringW(LOCALE_USER_DEFAULT
,
3405 NORM_IGNORECASE
| SORT_STRINGSORT
,
3406 args
, 2, L
"/D", -1) == CSTR_EQUAL
) {
3408 while (*args
&& (*args
==' ' || *args
=='\t'))
3412 GetCurrentDirectoryW(ARRAY_SIZE(cwd
), cwd
);
3415 lstrcatW(cwd
, L
"\r\n");
3416 WCMD_output_asis (cwd
);
3419 /* Remove any double quotes, which may be in the
3420 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3423 if (*args
!= '"') *pos
++ = *args
;
3426 while (pos
> string
&& (*(pos
-1) == ' ' || *(pos
-1) == '\t'))
3430 /* Search for appropriate directory */
3431 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
3432 hff
= FindFirstFileW(string
, &fd
);
3433 if (hff
!= INVALID_HANDLE_VALUE
) {
3435 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
3436 WCHAR fpath
[MAX_PATH
];
3438 WCHAR dir
[MAX_PATH
];
3439 WCHAR fname
[MAX_PATH
];
3440 WCHAR ext
[MAX_PATH
];
3442 /* Convert path into actual directory spec */
3443 if (!WCMD_get_fullpath(string
, ARRAY_SIZE(fpath
), fpath
, NULL
)) return;
3444 _wsplitpath(fpath
, drive
, dir
, fname
, ext
);
3447 wsprintfW(string
, L
"%s%s%s", drive
, dir
, fd
.cFileName
);
3450 } while (FindNextFileW(hff
, &fd
) != 0);
3454 /* Change to that directory */
3455 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
3457 status
= SetCurrentDirectoryW(string
);
3460 WCMD_print_error ();
3464 /* Save away the actual new directory, to store as current location */
3465 GetCurrentDirectoryW(ARRAY_SIZE(string
), string
);
3467 /* Restore old directory if drive letter would change, and
3468 CD x:\directory /D (or pushd c:\directory) not supplied */
3469 if ((wcsstr(quals
, L
"/D") == NULL
) &&
3470 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
3471 SetCurrentDirectoryW(cwd
);
3475 /* Set special =C: type environment variable, for drive letter of
3476 change of directory, even if path was restored due to missing
3477 /D (allows changing drive letter when not resident on that
3479 if ((string
[1] == ':') && IsCharAlphaW(string
[0])) {
3481 lstrcpyW(env
, L
"=");
3482 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
3484 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
3485 SetEnvironmentVariableW(env
, string
);
3492 /****************************************************************************
3495 * Set/Show the system date
3496 * FIXME: Can't change date yet
3499 void WCMD_setshow_date (void) {
3501 WCHAR curdate
[64], buffer
[64];
3505 if (GetDateFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, curdate
, ARRAY_SIZE(curdate
))) {
3506 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
3507 if (wcsstr(quals
, L
"/T") == NULL
) {
3508 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
3509 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, ARRAY_SIZE(buffer
), &count
);
3511 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
3515 else WCMD_print_error ();
3518 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
3522 /****************************************************************************
3524 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3527 static int __cdecl
WCMD_compare( const void *a
, const void *b
)
3530 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
3531 r
= CompareStringW( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
3532 *str_a
, wcscspn(*str_a
, L
"="), *str_b
, wcscspn(*str_b
, L
"=") );
3533 if( r
== CSTR_LESS_THAN
) return -1;
3534 if( r
== CSTR_GREATER_THAN
) return 1;
3538 /****************************************************************************
3539 * WCMD_setshow_sortenv
3541 * sort variables into order for display
3542 * Optionally only display those who start with a stub
3543 * returns the count displayed
3545 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
3547 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
3550 if (stub
) stublen
= lstrlenW(stub
);
3552 /* count the number of strings, and the total length */
3554 len
+= (lstrlenW(&s
[len
]) + 1);
3558 /* add the strings to an array */
3559 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
3563 for( i
=1; i
<count
; i
++ )
3564 str
[i
] = str
[i
-1] + lstrlenW(str
[i
-1]) + 1;
3566 /* sort the array */
3567 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
3570 for( i
=0; i
<count
; i
++ ) {
3571 if (!stub
|| CompareStringW(LOCALE_USER_DEFAULT
,
3572 NORM_IGNORECASE
| SORT_STRINGSORT
,
3573 str
[i
], stublen
, stub
, -1) == CSTR_EQUAL
) {
3574 /* Don't display special internal variables */
3575 if (str
[i
][0] != '=') {
3576 WCMD_output_asis(str
[i
]);
3577 WCMD_output_asis(L
"\r\n");
3584 return displayedcount
;
3587 /****************************************************************************
3588 * WCMD_getprecedence
3589 * Return the precedence of a particular operator
3591 static int WCMD_getprecedence(const WCHAR in
)
3632 /****************************************************************************
3634 * Push either a number or name (environment variable) onto the supplied
3637 static void WCMD_pushnumber(WCHAR
*var
, int num
, VARSTACK
**varstack
) {
3638 VARSTACK
*thisstack
= heap_xalloc(sizeof(VARSTACK
));
3639 thisstack
->isnum
= (var
== NULL
);
3641 thisstack
->variable
= var
;
3642 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var
));
3644 thisstack
->value
= num
;
3645 WINE_TRACE("Pushed number %d\n", num
);
3647 thisstack
->next
= *varstack
;
3648 *varstack
= thisstack
;
3651 /****************************************************************************
3653 * Returns the value of the top number or environment variable on the stack
3654 * and leaves the item on the stack.
3656 static int WCMD_peeknumber(VARSTACK
**varstack
) {
3661 thisvar
= *varstack
;
3662 if (!thisvar
->isnum
) {
3663 WCHAR tmpstr
[MAXSTRING
];
3664 if (GetEnvironmentVariableW(thisvar
->variable
, tmpstr
, MAXSTRING
)) {
3665 result
= wcstol(tmpstr
,NULL
,0);
3667 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar
->variable
), result
);
3669 result
= thisvar
->value
;
3672 WINE_TRACE("Peeked number %d\n", result
);
3676 /****************************************************************************
3678 * Returns the value of the top number or environment variable on the stack
3679 * and removes the item from the stack.
3681 static int WCMD_popnumber(VARSTACK
**varstack
) {
3686 thisvar
= *varstack
;
3687 result
= WCMD_peeknumber(varstack
);
3688 if (!thisvar
->isnum
) heap_free(thisvar
->variable
);
3689 *varstack
= thisvar
->next
;
3692 WINE_TRACE("Popped number %d\n", result
);
3696 /****************************************************************************
3698 * Push an operator onto the supplied stack
3700 static void WCMD_pushoperator(WCHAR op
, int precedence
, OPSTACK
**opstack
) {
3701 OPSTACK
*thisstack
= heap_xalloc(sizeof(OPSTACK
));
3702 thisstack
->precedence
= precedence
;
3704 thisstack
->next
= *opstack
;
3705 WINE_TRACE("Pushed operator %c\n", op
);
3706 *opstack
= thisstack
;
3709 /****************************************************************************
3711 * Returns the operator from the top of the stack and removes the item from
3714 static WCHAR
WCMD_popoperator(OPSTACK
**opstack
) {
3720 result
= thisop
->op
;
3721 *opstack
= thisop
->next
;
3724 WINE_TRACE("Popped operator %c\n", result
);
3728 /****************************************************************************
3730 * Actions the top operator on the stack against the first and sometimes
3731 * second value on the variable stack, and pushes the result
3732 * Returns non-zero on error.
3734 static int WCMD_reduce(OPSTACK
**opstack
, VARSTACK
**varstack
) {
3739 if (!*opstack
|| !*varstack
) {
3740 WINE_TRACE("No operators for the reduce\n");
3741 return WCMD_NOOPERATOR
;
3744 /* Remove the top operator */
3745 thisop
= WCMD_popoperator(opstack
);
3746 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop
);
3748 /* One variable operators */
3749 var1
= WCMD_popnumber(varstack
);
3751 case '!': WCMD_pushnumber(NULL
, !var1
, varstack
);
3753 case '~': WCMD_pushnumber(NULL
, ~var1
, varstack
);
3755 case OP_POSITIVE
: WCMD_pushnumber(NULL
, var1
, varstack
);
3757 case OP_NEGATIVE
: WCMD_pushnumber(NULL
, -var1
, varstack
);
3761 /* Two variable operators */
3763 WINE_TRACE("No operands left for the reduce?\n");
3764 return WCMD_NOOPERAND
;
3771 break; /* Handled above */
3772 case '*': var2
= WCMD_popnumber(varstack
);
3773 WCMD_pushnumber(NULL
, var2
*var1
, varstack
);
3775 case '/': var2
= WCMD_popnumber(varstack
);
3776 if (var1
== 0) return WCMD_DIVIDEBYZERO
;
3777 WCMD_pushnumber(NULL
, var2
/var1
, varstack
);
3779 case '+': var2
= WCMD_popnumber(varstack
);
3780 WCMD_pushnumber(NULL
, var2
+var1
, varstack
);
3782 case '-': var2
= WCMD_popnumber(varstack
);
3783 WCMD_pushnumber(NULL
, var2
-var1
, varstack
);
3785 case '&': var2
= WCMD_popnumber(varstack
);
3786 WCMD_pushnumber(NULL
, var2
&var1
, varstack
);
3788 case '%': var2
= WCMD_popnumber(varstack
);
3789 if (var1
== 0) return WCMD_DIVIDEBYZERO
;
3790 WCMD_pushnumber(NULL
, var2
%var1
, varstack
);
3792 case '^': var2
= WCMD_popnumber(varstack
);
3793 WCMD_pushnumber(NULL
, var2
^var1
, varstack
);
3795 case '<': var2
= WCMD_popnumber(varstack
);
3796 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3797 which differs from the compiler (for example gcc) so being explicit. */
3798 if (var1
< 0 || var1
>= (8 * sizeof(INT
))) {
3799 WCMD_pushnumber(NULL
, 0, varstack
);
3801 WCMD_pushnumber(NULL
, var2
<<var1
, varstack
);
3804 case '>': var2
= WCMD_popnumber(varstack
);
3805 WCMD_pushnumber(NULL
, var2
>>var1
, varstack
);
3807 case '|': var2
= WCMD_popnumber(varstack
);
3808 WCMD_pushnumber(NULL
, var2
|var1
, varstack
);
3824 /* The left of an equals must be one variable */
3825 if (!(*varstack
) || (*varstack
)->isnum
) {
3826 return WCMD_NOOPERAND
;
3829 /* Make the number stack grow by inserting the value of the variable */
3830 var2
= WCMD_peeknumber(varstack
);
3831 WCMD_pushnumber(NULL
, var2
, varstack
);
3832 WCMD_pushnumber(NULL
, var1
, varstack
);
3834 /* Make the operand stack grow by pushing the assign operator plus the
3835 operator to perform */
3836 while (calcassignments
[i
].op
!= ' ' &&
3837 calcassignments
[i
].calculatedop
!= thisop
) {
3840 if (calcassignments
[i
].calculatedop
== ' ') {
3841 WINE_ERR("Unexpected operator %c\n", thisop
);
3842 return WCMD_NOOPERATOR
;
3844 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack
);
3845 WCMD_pushoperator(calcassignments
[i
].op
,
3846 WCMD_getprecedence(calcassignments
[i
].op
), opstack
);
3852 WCHAR result
[MAXSTRING
];
3854 /* Build the result, then push it onto the stack */
3855 swprintf(result
, ARRAY_SIZE(result
), L
"%d", var1
);
3856 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack
)->variable
),
3857 wine_dbgstr_w(result
));
3858 SetEnvironmentVariableW((*varstack
)->variable
, result
);
3859 var2
= WCMD_popnumber(varstack
);
3860 WCMD_pushnumber(NULL
, var1
, varstack
);
3864 default: WINE_ERR("Unrecognized operator %c\n", thisop
);
3871 /****************************************************************************
3872 * WCMD_handleExpression
3873 * Handles an expression provided to set /a - If it finds brackets, it uses
3874 * recursion to process the parts in brackets.
3876 static int WCMD_handleExpression(WCHAR
**expr
, int *ret
, int depth
)
3878 static const WCHAR mathDelims
[] = L
" \t()!~-*/%+<>&^|=,";
3881 BOOL lastwasnumber
= FALSE
; /* FALSE makes a minus at the start of the expression easier to handle */
3882 OPSTACK
*opstackhead
= NULL
;
3883 VARSTACK
*varstackhead
= NULL
;
3884 WCHAR foundhalf
= 0;
3887 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr
));
3890 /* Iterate through until whole expression is processed */
3891 while (pos
&& *pos
) {
3894 /* Skip whitespace to get to the next character to process*/
3895 while (*pos
&& (*pos
==' ' || *pos
=='\t')) pos
++;
3896 if (!*pos
) goto exprreturn
;
3898 /* If we have found anything other than an operator then it's a number/variable */
3899 if (wcschr(mathDelims
, *pos
) == NULL
) {
3900 WCHAR
*parmstart
, *parm
, *dupparm
;
3903 /* Cannot have an expression with var/number twice, without an operator
3904 in-between, nor or number following a half constructed << or >> operator */
3905 if (lastwasnumber
|| foundhalf
) {
3906 rc
= WCMD_NOOPERATOR
;
3907 goto exprerrorreturn
;
3909 lastwasnumber
= TRUE
;
3911 if (iswdigit(*pos
)) {
3912 /* For a number - just push it onto the stack */
3913 int num
= wcstoul(pos
, &nextpos
, 0);
3914 WCMD_pushnumber(NULL
, num
, &varstackhead
);
3917 /* Verify the number was validly formed */
3918 if (*nextpos
&& (wcschr(mathDelims
, *nextpos
) == NULL
)) {
3919 rc
= WCMD_BADHEXOCT
;
3920 goto exprerrorreturn
;
3924 /* For a variable - just push it onto the stack */
3925 parm
= WCMD_parameter_with_delims(pos
, 0, &parmstart
, FALSE
, FALSE
, mathDelims
);
3926 dupparm
= heap_strdupW(parm
);
3927 WCMD_pushnumber(dupparm
, 0, &varstackhead
);
3928 pos
= parmstart
+ lstrlenW(dupparm
);
3933 /* We have found an operator. Some operators are one character, some two, and the minus
3934 and plus signs need special processing as they can be either operators or just influence
3935 the parameter which follows them */
3936 if (foundhalf
&& (*pos
!= foundhalf
)) {
3937 /* Badly constructed operator pair */
3938 rc
= WCMD_NOOPERATOR
;
3939 goto exprerrorreturn
;
3942 treatasnumber
= FALSE
; /* We are processing an operand */
3945 /* > and < are special as they are double character operators (and spaces can be between them!)
3946 If we see these for the first time, set a flag, and second time around we continue.
3947 Note these double character operators are stored as just one of the characters on the stack */
3949 case '<': if (!foundhalf
) {
3954 /* We have found the rest, so clear up the knowledge of the half completed part and
3955 drop through to normal operator processing */
3959 case '=': if (*pos
=='=') {
3960 /* = is special cased as if the last was an operator then we may have e.g. += or
3961 *= etc which we need to handle by replacing the operator that is on the stack
3962 with a calculated assignment equivalent */
3963 if (!lastwasnumber
&& opstackhead
) {
3965 while (calcassignments
[i
].op
!= ' ' && calcassignments
[i
].op
!= opstackhead
->op
) {
3968 if (calcassignments
[i
].op
== ' ') {
3969 rc
= WCMD_NOOPERAND
;
3970 goto exprerrorreturn
;
3972 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3973 when the general operator handling happens further down. */
3974 *pos
= calcassignments
[i
].calculatedop
;
3975 WCMD_popoperator(&opstackhead
);
3981 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3982 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3983 case '+': if (!lastwasnumber
&& *pos
=='+') *pos
= OP_POSITIVE
;
3985 case '-': if (!lastwasnumber
&& *pos
=='-') *pos
= OP_NEGATIVE
;
3988 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3989 case '!': /* drop through */
3990 case '~': /* drop through */
3991 case '/': /* drop through */
3992 case '%': /* drop through */
3993 case '&': /* drop through */
3994 case '^': /* drop through */
3995 case '*': /* drop through */
3997 /* General code for handling most of the operators - look at the
3998 precedence of the top item on the stack, and see if we need to
3999 action the stack before we push something else onto it. */
4001 int precedence
= WCMD_getprecedence(*pos
);
4002 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos
,
4003 precedence
, !opstackhead
?-1:opstackhead
->precedence
);
4005 /* In general, for things with the same precedence, reduce immediately
4006 except for assignments and unary operators which do not */
4007 while (!rc
&& opstackhead
&&
4008 ((opstackhead
->precedence
> precedence
) ||
4009 ((opstackhead
->precedence
== precedence
) &&
4010 (precedence
!= 1) && (precedence
!= 8)))) {
4011 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
4013 if (rc
) goto exprerrorreturn
;
4014 WCMD_pushoperator(*pos
, precedence
, &opstackhead
);
4019 /* comma means start a new expression, ie calculate what we have */
4022 int prevresult
= -1;
4023 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
4024 while (!rc
&& opstackhead
) {
4025 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
4027 if (rc
) goto exprerrorreturn
;
4028 /* If we have anything other than one number left, error
4029 otherwise throw the number away */
4030 if (!varstackhead
|| varstackhead
->next
) {
4031 rc
= WCMD_NOOPERATOR
;
4032 goto exprerrorreturn
;
4034 prevresult
= WCMD_popnumber(&varstackhead
);
4035 WINE_TRACE("Expression resolved to %d\n", prevresult
);
4036 heap_free(varstackhead
);
4037 varstackhead
= NULL
;
4042 /* Open bracket - use iteration to parse the inner expression, then continue */
4046 rc
= WCMD_handleExpression(&pos
, &exprresult
, depth
+1);
4047 if (rc
) goto exprerrorreturn
;
4048 WCMD_pushnumber(NULL
, exprresult
, &varstackhead
);
4052 /* Close bracket - we have finished this depth, calculate and return */
4055 treatasnumber
= TRUE
; /* Things in brackets result in a number */
4058 goto exprerrorreturn
;
4064 WINE_ERR("Unrecognized operator %c\n", *pos
);
4067 lastwasnumber
= treatasnumber
;
4073 /* We need to reduce until we have a single number (or variable) on the
4074 stack and set the return value to that */
4075 while (!rc
&& opstackhead
) {
4076 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
4078 if (rc
) goto exprerrorreturn
;
4080 /* If we have anything other than one number left, error
4081 otherwise throw the number away */
4082 if (!varstackhead
|| varstackhead
->next
) {
4083 rc
= WCMD_NOOPERATOR
;
4084 goto exprerrorreturn
;
4087 /* Now get the number (and convert if it's just a variable name) */
4088 *ret
= WCMD_popnumber(&varstackhead
);
4091 /* Free all remaining memory */
4092 while (opstackhead
) WCMD_popoperator(&opstackhead
);
4093 while (varstackhead
) WCMD_popnumber(&varstackhead
);
4095 WINE_TRACE("Returning result %d, rc %d\n", *ret
, rc
);
4099 /****************************************************************************
4102 * Set/Show the environment variables
4105 void WCMD_setshow_env (WCHAR
*s
) {
4110 WCHAR string
[MAXSTRING
];
4112 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
4113 env
= GetEnvironmentStringsW();
4114 WCMD_setshow_sortenv( env
, NULL
);
4118 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4119 if (CompareStringW(LOCALE_USER_DEFAULT
,
4120 NORM_IGNORECASE
| SORT_STRINGSORT
,
4121 s
, 2, L
"/P", -1) == CSTR_EQUAL
) {
4125 while (*s
&& (*s
==' ' || *s
=='\t')) s
++;
4126 /* set /P "var=value"jim ignores anything after the last quote */
4129 lastquote
= WCMD_strip_quotes(s
);
4130 if (lastquote
) *lastquote
= 0x00;
4131 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s
));
4134 /* If no parameter, or no '=' sign, return an error */
4135 if (!(*s
) || ((p
= wcschr (s
, '=')) == NULL
)) {
4136 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
4140 /* Output the prompt */
4142 if (*p
) WCMD_output_asis(p
);
4144 /* Read the reply */
4145 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, ARRAY_SIZE(string
), &count
);
4147 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
4148 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
4149 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
4150 wine_dbgstr_w(string
));
4151 SetEnvironmentVariableW(s
, string
);
4154 /* See if /A supplied, and if so calculate the results of all the expressions */
4155 } else if (CompareStringW(LOCALE_USER_DEFAULT
,
4156 NORM_IGNORECASE
| SORT_STRINGSORT
,
4157 s
, 2, L
"/A", -1) == CSTR_EQUAL
) {
4158 /* /A supplied, so evaluate expressions and set variables appropriately */
4159 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4160 /* of the final computation */
4166 /* Remove all quotes before doing any calculations */
4167 thisexpr
= heap_xalloc((lstrlenW(s
+2)+1) * sizeof(WCHAR
));
4171 if (*src
!= '"') *dst
++ = *src
;
4176 /* Now calculate the results of the expression */
4178 rc
= WCMD_handleExpression(&src
, &result
, 0);
4179 heap_free(thisexpr
);
4181 /* If parsing failed, issue the error message */
4183 WCMD_output_stderr(WCMD_LoadMessage(rc
));
4187 /* If we have no context (interactive or cmd.exe /c) print the final result */
4189 swprintf(string
, ARRAY_SIZE(string
), L
"%d", result
);
4190 WCMD_output_asis(string
);
4196 /* set "var=value"jim ignores anything after the last quote */
4199 lastquote
= WCMD_strip_quotes(s
);
4200 if (lastquote
) *lastquote
= 0x00;
4201 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s
));
4204 p
= wcschr (s
, '=');
4206 env
= GetEnvironmentStringsW();
4207 if (WCMD_setshow_sortenv( env
, s
) == 0) {
4208 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
4216 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
4218 status
= SetEnvironmentVariableW(s
, p
);
4219 gle
= GetLastError();
4220 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
4222 } else if (!status
) WCMD_print_error();
4223 else if (!interactive
) errorlevel
= 0;
4227 /****************************************************************************
4230 * Set/Show the path environment variable
4233 void WCMD_setshow_path (const WCHAR
*args
) {
4238 if (!*param1
&& !*param2
) {
4239 status
= GetEnvironmentVariableW(L
"PATH", string
, ARRAY_SIZE(string
));
4241 WCMD_output_asis(L
"PATH=");
4242 WCMD_output_asis ( string
);
4243 WCMD_output_asis(L
"\r\n");
4246 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH
));
4250 if (*args
== '=') args
++; /* Skip leading '=' */
4251 status
= SetEnvironmentVariableW(L
"PATH", args
);
4252 if (!status
) WCMD_print_error();
4256 /****************************************************************************
4257 * WCMD_setshow_prompt
4259 * Set or show the command prompt.
4262 void WCMD_setshow_prompt (void) {
4267 SetEnvironmentVariableW(L
"PROMPT", NULL
);
4271 while ((*s
== '=') || (*s
== ' ') || (*s
== '\t')) s
++;
4273 SetEnvironmentVariableW(L
"PROMPT", NULL
);
4275 else SetEnvironmentVariableW(L
"PROMPT", s
);
4279 /****************************************************************************
4282 * Set/Show the system time
4283 * FIXME: Can't change time yet
4286 void WCMD_setshow_time (void) {
4288 WCHAR curtime
[64], buffer
[64];
4294 if (GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &st
, NULL
, curtime
, ARRAY_SIZE(curtime
))) {
4295 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME
), curtime
);
4296 if (wcsstr(quals
, L
"/T") == NULL
) {
4297 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
4298 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, ARRAY_SIZE(buffer
), &count
);
4300 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
4304 else WCMD_print_error ();
4307 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
4311 /****************************************************************************
4314 * Shift batch parameters.
4315 * Optional /n says where to start shifting (n=0-8)
4318 void WCMD_shift (const WCHAR
*args
) {
4321 if (context
!= NULL
) {
4322 WCHAR
*pos
= wcschr(args
, '/');
4327 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
4328 start
= (*(pos
+1) - '0');
4330 SetLastError(ERROR_INVALID_PARAMETER
);
4335 WINE_TRACE("Shifting variables, starting at %d\n", start
);
4336 for (i
=start
;i
<=8;i
++) {
4337 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
4339 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
4344 /****************************************************************************
4347 void WCMD_start(WCHAR
*args
)
4351 WCHAR file
[MAX_PATH
];
4352 WCHAR
*cmdline
, *cmdline_params
;
4354 PROCESS_INFORMATION pi
;
4356 GetSystemDirectoryW( file
, MAX_PATH
);
4357 lstrcatW(file
, L
"\\start.exe");
4358 cmdline
= heap_xalloc( (lstrlenW(file
) + lstrlenW(args
) + 8) * sizeof(WCHAR
) );
4359 lstrcpyW( cmdline
, file
);
4360 lstrcatW(cmdline
, L
" ");
4361 cmdline_params
= cmdline
+ lstrlenW(cmdline
);
4363 /* The start built-in has some special command-line parsing properties
4364 * which will be outlined here.
4366 * both '\t' and ' ' are argument separators
4367 * '/' has a special double role as both separator and switch prefix, e.g.
4373 * are valid ways to pass multiple options to start. In the latter case
4374 * '/i' is not a part of the title but parsed as a switch.
4376 * However, '=', ';' and ',' are not separators:
4377 * > start "deus"=ex,machina
4379 * will in fact open a console titled 'deus=ex,machina'
4381 * The title argument parsing code is only interested in quotes themselves,
4382 * it does not respect escaping of any kind and all quotes are dropped
4383 * from the resulting title, therefore:
4385 * > start "\"" hello"/low
4387 * actually opens a console titled '\ hello' with low priorities.
4389 * To not break compatibility with wine programs relying on
4390 * wine's separate 'start.exe', this program's peculiar console
4391 * title parsing is actually implemented in 'cmd.exe' which is the
4392 * application native Windows programs will use to invoke 'start'.
4394 * WCMD_parameter_with_delims will take care of everything for us.
4397 for (argno
=0; ; argno
++) {
4398 WCHAR
*thisArg
, *argN
;
4401 thisArg
= WCMD_parameter_with_delims(args
, argno
, &argN
, FALSE
, FALSE
, L
" \t/");
4403 /* No more parameters */
4407 /* Found the title */
4408 if (argN
[0] == '"') {
4409 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg
));
4412 /* Copy all of the cmdline processed */
4413 memcpy(cmdline_params
, args
, sizeof(WCHAR
) * (argN
- args
));
4414 cmdline_params
[argN
- args
] = '\0';
4416 /* Add quoted title */
4417 lstrcatW(cmdline_params
, L
"\"\\\"");
4418 lstrcatW(cmdline_params
, thisArg
);
4419 lstrcatW(cmdline_params
, L
"\\\"\"");
4421 /* Concatenate remaining command-line */
4422 thisArg
= WCMD_parameter_with_delims(args
, argno
, &argN
, TRUE
, FALSE
, L
" \t/");
4423 lstrcatW(cmdline_params
, argN
+ lstrlenW(thisArg
));
4428 /* Skipping a regular argument? */
4429 else if (argN
!= args
&& argN
[-1] == '/') {
4432 /* Not an argument nor the title, start of program arguments,
4433 * stop looking for title.
4439 /* build command-line if not built yet */
4441 lstrcatW( cmdline
, args
);
4444 memset( &st
, 0, sizeof(STARTUPINFOW
) );
4445 st
.cb
= sizeof(STARTUPINFOW
);
4447 if (CreateProcessW( file
, cmdline
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pi
))
4449 WaitForSingleObject( pi
.hProcess
, INFINITE
);
4450 GetExitCodeProcess( pi
.hProcess
, &errorlevel
);
4451 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
4452 CloseHandle(pi
.hProcess
);
4453 CloseHandle(pi
.hThread
);
4457 SetLastError(ERROR_FILE_NOT_FOUND
);
4458 WCMD_print_error ();
4464 /****************************************************************************
4467 * Set the console title
4469 void WCMD_title (const WCHAR
*args
) {
4470 SetConsoleTitleW(args
);
4473 /****************************************************************************
4476 * Copy a file to standard output.
4479 void WCMD_type (WCHAR
*args
) {
4483 BOOL writeHeaders
= FALSE
;
4485 if (param1
[0] == 0x00) {
4486 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
4490 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
4492 /* Loop through all args */
4495 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
4503 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
4504 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4505 FILE_ATTRIBUTE_NORMAL
, NULL
);
4506 if (h
== INVALID_HANDLE_VALUE
) {
4507 WCMD_print_error ();
4508 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
4512 WCMD_output_stderr(L
"\n%1\n\n\n", thisArg
);
4514 while (WCMD_ReadFile(h
, buffer
, ARRAY_SIZE(buffer
) - 1, &count
)) {
4515 if (count
== 0) break; /* ReadFile reports success on EOF! */
4517 WCMD_output_asis (buffer
);
4524 /****************************************************************************
4527 * Output either a file or stdin to screen in pages
4530 void WCMD_more (WCHAR
*args
) {
4535 WCHAR moreStrPage
[100];
4539 /* Prefix the NLS more with '-- ', then load the text */
4541 lstrcpyW(moreStr
, L
"-- ");
4542 LoadStringW(hinst
, WCMD_MORESTR
, &moreStr
[3], ARRAY_SIZE(moreStr
)-3);
4544 if (param1
[0] == 0x00) {
4546 /* Wine implements pipes via temporary files, and hence stdin is
4547 effectively reading from the file. This means the prompts for
4548 more are satisfied by the next line from the input (file). To
4549 avoid this, ensure stdin is to the console */
4550 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
4551 HANDLE hConIn
= CreateFileW(L
"CONIN$", GENERIC_READ
| GENERIC_WRITE
,
4552 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4553 FILE_ATTRIBUTE_NORMAL
, 0);
4554 WINE_TRACE("No parms - working probably in pipe mode\n");
4555 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
4557 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4558 once you get in this bit unless due to a pipe, it's going to end badly... */
4559 wsprintfW(moreStrPage
, L
"%s --\n", moreStr
);
4561 WCMD_enter_paged_mode(moreStrPage
);
4562 while (WCMD_ReadFile(hstdin
, buffer
, ARRAY_SIZE(buffer
)-1, &count
)) {
4563 if (count
== 0) break; /* ReadFile reports success on EOF! */
4565 WCMD_output_asis (buffer
);
4567 WCMD_leave_paged_mode();
4569 /* Restore stdin to what it was */
4570 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
4571 CloseHandle(hConIn
);
4575 BOOL needsPause
= FALSE
;
4577 /* Loop through all args */
4578 WINE_TRACE("Parms supplied - working through each file\n");
4579 WCMD_enter_paged_mode(moreStrPage
);
4582 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
4590 wsprintfW(moreStrPage
, L
"%s (%2.2d%%) --\n", moreStr
, 100);
4591 WCMD_leave_paged_mode();
4592 WCMD_output_asis(moreStrPage
);
4593 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, ARRAY_SIZE(buffer
), &count
);
4594 WCMD_enter_paged_mode(moreStrPage
);
4598 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
4599 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4600 FILE_ATTRIBUTE_NORMAL
, NULL
);
4601 if (h
== INVALID_HANDLE_VALUE
) {
4602 WCMD_print_error ();
4603 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
4607 ULONG64 fileLen
= 0;
4608 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
4610 /* Get the file size */
4611 GetFileAttributesExW(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
4612 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
4615 while (WCMD_ReadFile(h
, buffer
, ARRAY_SIZE(buffer
)-1, &count
)) {
4616 if (count
== 0) break; /* ReadFile reports success on EOF! */
4620 /* Update % count (would be used in WCMD_output_asis as prompt) */
4621 wsprintfW(moreStrPage
, L
"%s (%2.2d%%) --\n", moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
4623 WCMD_output_asis (buffer
);
4629 WCMD_leave_paged_mode();
4633 /****************************************************************************
4636 * Display verify flag.
4637 * FIXME: We don't actually do anything with the verify flag other than toggle
4641 void WCMD_verify (const WCHAR
*args
) {
4645 count
= lstrlenW(args
);
4647 if (verify_mode
) WCMD_output(WCMD_LoadMessage(WCMD_VERIFYPROMPT
), L
"ON");
4648 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), L
"OFF");
4651 if (lstrcmpiW(args
, L
"ON") == 0) {
4655 else if (lstrcmpiW(args
, L
"OFF") == 0) {
4656 verify_mode
= FALSE
;
4659 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR
));
4662 /****************************************************************************
4665 * Display version info.
4668 void WCMD_version (void) {
4670 WCMD_output_asis (version_string
);
4674 /****************************************************************************
4677 * Display volume information (set_label = FALSE)
4678 * Additionally set volume label (set_label = TRUE)
4679 * Returns 1 on success, 0 otherwise
4682 int WCMD_volume(BOOL set_label
, const WCHAR
*path
)
4684 DWORD count
, serial
;
4685 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
4689 status
= GetCurrentDirectoryW(ARRAY_SIZE(curdir
), curdir
);
4691 WCMD_print_error ();
4694 status
= GetVolumeInformationW(NULL
, label
, ARRAY_SIZE(label
), &serial
, NULL
, NULL
, NULL
, 0);
4697 if ((path
[1] != ':') || (lstrlenW(path
) != 2)) {
4698 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
4701 wsprintfW (curdir
, L
"%s\\", path
);
4702 status
= GetVolumeInformationW(curdir
, label
, ARRAY_SIZE(label
), &serial
, NULL
, NULL
, NULL
, 0);
4705 WCMD_print_error ();
4708 if (label
[0] != '\0') {
4709 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL
),
4713 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL
),
4716 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO
),
4717 HIWORD(serial
), LOWORD(serial
));
4719 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
4720 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, ARRAY_SIZE(string
), &count
);
4722 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
4723 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
4726 if (!SetVolumeLabelW(curdir
, string
)) WCMD_print_error ();
4729 if (!SetVolumeLabelW(NULL
, string
)) WCMD_print_error ();
4735 /**************************************************************************
4738 * Exit either the process, or just this batch program
4742 void WCMD_exit (CMD_LIST
**cmdList
) {
4743 int rc
= wcstol(param1
, NULL
, 10); /* Note: wcstol of empty parameter is 0 */
4745 if (context
&& lstrcmpiW(quals
, L
"/B") == 0) {
4747 context
-> skip_rest
= TRUE
;
4755 /*****************************************************************************
4758 * Lists or sets file associations (assoc = TRUE)
4759 * Lists or sets file types (assoc = FALSE)
4761 void WCMD_assoc (const WCHAR
*args
, BOOL assoc
) {
4764 DWORD accessOptions
= KEY_READ
;
4766 LONG rc
= ERROR_SUCCESS
;
4767 WCHAR keyValue
[MAXSTRING
];
4771 /* See if parameter includes '=' */
4773 newValue
= wcschr(args
, '=');
4774 if (newValue
) accessOptions
|= KEY_WRITE
;
4776 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4777 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, L
"", 0, accessOptions
, &key
) != ERROR_SUCCESS
) {
4778 WINE_FIXME("Unexpected failure opening HKCR key: %ld\n", GetLastError());
4782 /* If no parameters then list all associations */
4783 if (*args
== 0x00) {
4786 /* Enumerate all the keys */
4787 while (rc
!= ERROR_NO_MORE_ITEMS
) {
4788 WCHAR keyName
[MAXSTRING
];
4791 /* Find the next value */
4792 nameLen
= MAXSTRING
;
4793 rc
= RegEnumKeyExW(key
, index
++, keyName
, &nameLen
, NULL
, NULL
, NULL
, NULL
);
4795 if (rc
== ERROR_SUCCESS
) {
4797 /* Only interested in extension ones if assoc, or others
4799 if ((keyName
[0] == '.' && assoc
) ||
4800 (!(keyName
[0] == '.') && (!assoc
)))
4802 WCHAR subkey
[MAXSTRING
];
4803 lstrcpyW(subkey
, keyName
);
4804 if (!assoc
) lstrcatW(subkey
, L
"\\Shell\\Open\\Command");
4806 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
4808 valueLen
= sizeof(keyValue
);
4809 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
4810 WCMD_output_asis(keyName
);
4811 WCMD_output_asis(L
"=");
4812 /* If no default value found, leave line empty after '=' */
4813 if (rc
== ERROR_SUCCESS
) {
4814 WCMD_output_asis(keyValue
);
4816 WCMD_output_asis(L
"\r\n");
4817 RegCloseKey(readKey
);
4825 /* Parameter supplied - if no '=' on command line, it's a query */
4826 if (newValue
== NULL
) {
4828 WCHAR subkey
[MAXSTRING
];
4830 /* Query terminates the parameter at the first space */
4831 lstrcpyW(keyValue
, args
);
4832 space
= wcschr(keyValue
, ' ');
4833 if (space
) *space
=0x00;
4835 /* Set up key name */
4836 lstrcpyW(subkey
, keyValue
);
4837 if (!assoc
) lstrcatW(subkey
, L
"\\Shell\\Open\\Command");
4839 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
4841 valueLen
= sizeof(keyValue
);
4842 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
4843 WCMD_output_asis(args
);
4844 WCMD_output_asis(L
"=");
4845 /* If no default value found, leave line empty after '=' */
4846 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
4847 WCMD_output_asis(L
"\r\n");
4848 RegCloseKey(readKey
);
4851 WCHAR msgbuffer
[MAXSTRING
];
4853 /* Load the translated 'File association not found' */
4855 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, ARRAY_SIZE(msgbuffer
));
4857 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, ARRAY_SIZE(msgbuffer
));
4859 WCMD_output_stderr(msgbuffer
, keyValue
);
4863 /* Not a query - it's a set or clear of a value */
4866 WCHAR subkey
[MAXSTRING
];
4868 /* Get pointer to new value */
4872 /* Set up key name */
4873 lstrcpyW(subkey
, args
);
4874 if (!assoc
) lstrcatW(subkey
, L
"\\Shell\\Open\\Command");
4876 /* If nothing after '=' then clear value - only valid for ASSOC */
4877 if (*newValue
== 0x00) {
4879 if (assoc
) rc
= RegDeleteKeyW(key
, args
);
4880 if (assoc
&& rc
== ERROR_SUCCESS
) {
4881 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args
));
4883 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
4888 WCHAR msgbuffer
[MAXSTRING
];
4890 /* Load the translated 'File association not found' */
4892 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, ARRAY_SIZE(msgbuffer
));
4894 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, ARRAY_SIZE(msgbuffer
));
4896 WCMD_output_stderr(msgbuffer
, args
);
4900 /* It really is a set value = contents */
4902 rc
= RegCreateKeyExW(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
4903 accessOptions
, NULL
, &readKey
, NULL
);
4904 if (rc
== ERROR_SUCCESS
) {
4905 rc
= RegSetValueExW(readKey
, NULL
, 0, REG_SZ
,
4907 sizeof(WCHAR
) * (lstrlenW(newValue
) + 1));
4908 RegCloseKey(readKey
);
4911 if (rc
!= ERROR_SUCCESS
) {
4915 WCMD_output_asis(args
);
4916 WCMD_output_asis(L
"=");
4917 WCMD_output_asis(newValue
);
4918 WCMD_output_asis(L
"\r\n");
4928 /****************************************************************************
4931 * Colors the terminal screen.
4934 void WCMD_color (void) {
4936 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
4937 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
4939 if (param1
[0] != 0x00 && lstrlenW(param1
) > 2) {
4940 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR
));
4944 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
4950 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
4955 /* Convert the color hex digits */
4956 if (param1
[0] == 0x00) {
4957 color
= defaultColor
;
4959 color
= wcstoul(param1
, NULL
, 16);
4962 /* Fail if fg == bg color */
4963 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
4968 /* Set the current screen contents and ensure all future writes
4969 remain this color */
4970 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
4971 SetConsoleTextAttribute(hStdOut
, color
);
4975 /****************************************************************************
4979 void WCMD_mklink(WCHAR
*args
)
4984 BOOL junction
= FALSE
;
4987 WCHAR file1
[MAX_PATH
];
4988 WCHAR file2
[MAX_PATH
];
4990 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
4991 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
4998 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
5002 WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
5004 if (lstrcmpiW(thisArg
, L
"/D") == 0)
5006 else if (lstrcmpiW(thisArg
, L
"/H") == 0)
5008 else if (lstrcmpiW(thisArg
, L
"/J") == 0)
5012 lstrcpyW(file1
, thisArg
);
5014 lstrcpyW(file2
, thisArg
);
5019 ret
= CreateHardLinkW(file1
, file2
, NULL
);
5021 ret
= CreateSymbolicLinkW(file1
, file2
, isdir
);
5023 WINE_TRACE("Juction links currently not supported.\n");
5026 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), file1
);