2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd
);
37 extern int defaultColor
;
38 extern BOOL echo_mode
;
39 extern BOOL interactive
;
41 struct env_stack
*pushd_directories
;
42 const WCHAR dotW
[] = {'.','\0'};
43 const WCHAR dotdotW
[] = {'.','.','\0'};
44 const WCHAR nullW
[] = {'\0'};
45 const WCHAR starW
[] = {'*','\0'};
46 const WCHAR slashW
[] = {'\\','\0'};
47 const WCHAR equalW
[] = {'=','\0'};
48 const WCHAR wildcardsW
[] = {'*','?','\0'};
49 const WCHAR slashstarW
[] = {'\\','*','\0'};
50 const WCHAR deviceW
[] = {'\\','\\','.','\\','\0'};
51 const WCHAR inbuilt
[][10] = {
52 {'C','A','L','L','\0'},
54 {'C','H','D','I','R','\0'},
56 {'C','O','P','Y','\0'},
57 {'C','T','T','Y','\0'},
58 {'D','A','T','E','\0'},
61 {'E','C','H','O','\0'},
62 {'E','R','A','S','E','\0'},
64 {'G','O','T','O','\0'},
65 {'H','E','L','P','\0'},
67 {'L','A','B','E','L','\0'},
69 {'M','K','D','I','R','\0'},
70 {'M','O','V','E','\0'},
71 {'P','A','T','H','\0'},
72 {'P','A','U','S','E','\0'},
73 {'P','R','O','M','P','T','\0'},
76 {'R','E','N','A','M','E','\0'},
78 {'R','M','D','I','R','\0'},
80 {'S','H','I','F','T','\0'},
81 {'S','T','A','R','T','\0'},
82 {'T','I','M','E','\0'},
83 {'T','I','T','L','E','\0'},
84 {'T','Y','P','E','\0'},
85 {'V','E','R','I','F','Y','\0'},
88 {'E','N','D','L','O','C','A','L','\0'},
89 {'S','E','T','L','O','C','A','L','\0'},
90 {'P','U','S','H','D','\0'},
91 {'P','O','P','D','\0'},
92 {'A','S','S','O','C','\0'},
93 {'C','O','L','O','R','\0'},
94 {'F','T','Y','P','E','\0'},
95 {'M','O','R','E','\0'},
96 {'C','H','O','I','C','E','\0'},
97 {'M','K','L','I','N','K','\0'},
98 {'E','X','I','T','\0'}
100 static const WCHAR externals
[][10] = {
101 {'A','T','T','R','I','B','\0'},
102 {'X','C','O','P','Y','\0'}
104 static const WCHAR onW
[] = {'O','N','\0'};
105 static const WCHAR offW
[] = {'O','F','F','\0'};
106 static const WCHAR parmY
[] = {'/','Y','\0'};
107 static const WCHAR parmNoY
[] = {'/','-','Y','\0'};
108 static const WCHAR eqeqW
[] = {'=','=','\0'};
110 static HINSTANCE hinst
;
111 struct env_stack
*saved_environment
;
112 static BOOL verify_mode
= FALSE
;
114 /* set /a routines work from single character operators, but some of the
115 operators are multiple character ones, especially the assignment ones.
116 Temporarily represent these using the values below on the operator stack */
117 #define OP_POSITIVE 'P'
118 #define OP_NEGATIVE 'N'
119 #define OP_ASSSIGNMUL 'a'
120 #define OP_ASSSIGNDIV 'b'
121 #define OP_ASSSIGNMOD 'c'
122 #define OP_ASSSIGNADD 'd'
123 #define OP_ASSSIGNSUB 'e'
124 #define OP_ASSSIGNAND 'f'
125 #define OP_ASSSIGNNOT 'g'
126 #define OP_ASSSIGNOR 'h'
127 #define OP_ASSSIGNSHL 'i'
128 #define OP_ASSSIGNSHR 'j'
130 /* This maintains a stack of operators, holding both the operator precedence
131 and the single character representation of the operator in question */
132 typedef struct _OPSTACK
136 struct _OPSTACK
*next
;
139 /* This maintains a stack of values, where each value can either be a
140 numeric value, or a string representing an environment variable */
141 typedef struct _VARSTACK
146 struct _VARSTACK
*next
;
149 /* This maintains a mapping between the calculated operator and the
150 single character representation for the assignment operators. */
155 } calcassignments
[] =
157 {'*', OP_ASSSIGNMUL
},
158 {'/', OP_ASSSIGNDIV
},
159 {'%', OP_ASSSIGNMOD
},
160 {'+', OP_ASSSIGNADD
},
161 {'-', OP_ASSSIGNSUB
},
162 {'&', OP_ASSSIGNAND
},
163 {'^', OP_ASSSIGNNOT
},
165 {'<', OP_ASSSIGNSHL
},
166 {'>', OP_ASSSIGNSHR
},
170 /**************************************************************************
173 * Issue a message and ask for confirmation, waiting on a valid answer.
175 * Returns True if Y (or A) answer is selected
176 * If optionAll contains a pointer, ALL is allowed, and if answered
180 static BOOL
WCMD_ask_confirm (const WCHAR
*message
, BOOL showSureText
,
184 WCHAR confirm
[MAXSTRING
];
185 WCHAR options
[MAXSTRING
];
186 WCHAR Ybuffer
[MAXSTRING
];
187 WCHAR Nbuffer
[MAXSTRING
];
188 WCHAR Abuffer
[MAXSTRING
];
189 WCHAR answer
[MAX_PATH
] = {'\0'};
192 /* Load the translated valid answers */
194 LoadStringW(hinst
, WCMD_CONFIRM
, confirm
, ARRAY_SIZE(confirm
));
195 msgid
= optionAll
? WCMD_YESNOALL
: WCMD_YESNO
;
196 LoadStringW(hinst
, msgid
, options
, ARRAY_SIZE(options
));
197 LoadStringW(hinst
, WCMD_YES
, Ybuffer
, ARRAY_SIZE(Ybuffer
));
198 LoadStringW(hinst
, WCMD_NO
, Nbuffer
, ARRAY_SIZE(Nbuffer
));
199 LoadStringW(hinst
, WCMD_ALL
, Abuffer
, ARRAY_SIZE(Abuffer
));
201 /* Loop waiting on a valid answer */
206 WCMD_output_asis (message
);
208 WCMD_output_asis (confirm
);
209 WCMD_output_asis (options
);
210 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, ARRAY_SIZE(answer
), &count
);
211 answer
[0] = towupper(answer
[0]);
212 if (answer
[0] == Ybuffer
[0])
214 if (answer
[0] == Nbuffer
[0])
216 if (optionAll
&& answer
[0] == Abuffer
[0])
224 /****************************************************************************
227 * Clear the terminal screen.
230 void WCMD_clear_screen (void) {
232 /* Emulate by filling the screen from the top left to bottom right with
233 spaces, then moving the cursor to the top left afterwards */
234 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
235 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
237 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
240 DWORD screenSize
, written
;
242 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
246 FillConsoleOutputCharacterW(hStdOut
, ' ', screenSize
, topLeft
, &written
);
247 FillConsoleOutputAttribute(hStdOut
, consoleInfo
.wAttributes
, screenSize
, topLeft
, &written
);
248 SetConsoleCursorPosition(hStdOut
, topLeft
);
252 /****************************************************************************
255 * Change the default i/o device (ie redirect STDin/STDout).
258 void WCMD_change_tty (void) {
260 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
264 /****************************************************************************
269 void WCMD_choice (const WCHAR
* args
) {
271 static const WCHAR bellW
[] = {7,0};
272 static const WCHAR commaW
[] = {',',0};
273 static const WCHAR bracket_open
[] = {'[',0};
274 static const WCHAR bracket_close
[] = {']','?',0};
279 WCHAR
*my_command
= NULL
;
280 WCHAR opt_default
= 0;
281 DWORD opt_timeout
= 0;
288 have_console
= GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), &oldmode
);
291 my_command
= heap_strdupW(WCMD_skip_leading_spaces((WCHAR
*) args
));
293 ptr
= WCMD_skip_leading_spaces(my_command
);
294 while (*ptr
== '/') {
295 switch (towupper(ptr
[1])) {
298 /* the colon is optional */
302 if (!*ptr
|| iswspace(*ptr
)) {
303 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr
));
304 heap_free(my_command
);
308 /* remember the allowed keys (overwrite previous /C option) */
310 while (*ptr
&& (!iswspace(*ptr
)))
314 /* terminate allowed chars */
316 ptr
= WCMD_skip_leading_spaces(&ptr
[1]);
318 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c
));
323 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
328 ptr
= WCMD_skip_leading_spaces(&ptr
[2]);
333 /* the colon is optional */
337 opt_default
= *ptr
++;
339 if (!opt_default
|| (*ptr
!= ',')) {
340 WINE_FIXME("bad option %s for /T\n", opt_default
? wine_dbgstr_w(ptr
) : "");
341 heap_free(my_command
);
347 while (((answer
[count
] = *ptr
)) && iswdigit(*ptr
) && (count
< 15)) {
353 opt_timeout
= wcstol(answer
, NULL
, 10);
355 ptr
= WCMD_skip_leading_spaces(ptr
);
359 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr
));
360 heap_free(my_command
);
366 WINE_FIXME("timeout not supported: %c,%d\n", opt_default
, opt_timeout
);
369 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), 0);
371 /* use default keys, when needed: localized versions of "Y"es and "No" */
373 LoadStringW(hinst
, WCMD_YES
, buffer
, ARRAY_SIZE(buffer
));
374 LoadStringW(hinst
, WCMD_NO
, buffer
+ 1, ARRAY_SIZE(buffer
) - 1);
379 /* print the question, when needed */
381 WCMD_output_asis(ptr
);
385 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c
));
389 /* print a list of all allowed answers inside brackets */
390 WCMD_output_asis(bracket_open
);
393 while ((answer
[0] = *ptr
++)) {
394 WCMD_output_asis(answer
);
396 WCMD_output_asis(commaW
);
398 WCMD_output_asis(bracket_close
);
403 /* FIXME: Add support for option /T */
404 answer
[1] = 0; /* terminate single character string */
405 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), answer
, 1, &count
);
408 answer
[0] = towupper(answer
[0]);
410 ptr
= wcschr(opt_c
, answer
[0]);
412 WCMD_output_asis(answer
);
413 WCMD_output_asis(newlineW
);
415 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE
), oldmode
);
417 errorlevel
= (ptr
- opt_c
) + 1;
418 WINE_TRACE("answer: %d\n", errorlevel
);
419 heap_free(my_command
);
424 /* key not allowed: play the bell */
425 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer
));
426 WCMD_output_asis(bellW
);
431 /****************************************************************************
434 * Adds an EOF onto the end of a file
435 * Returns TRUE on success
437 static BOOL
WCMD_AppendEOF(WCHAR
*filename
)
444 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename
));
445 h
= CreateFileW(filename
, GENERIC_WRITE
, 0, NULL
,
446 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
448 if (h
== INVALID_HANDLE_VALUE
) {
449 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename
), GetLastError());
452 SetFilePointer (h
, 0, NULL
, FILE_END
);
453 if (!WriteFile(h
, &eof
, 1, &bytes_written
, NULL
)) {
454 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename
), GetLastError());
463 /****************************************************************************
466 * Checks if the two paths reference to the same file
468 static BOOL
WCMD_IsSameFile(const WCHAR
*name1
, const WCHAR
*name2
)
471 HANDLE file1
= INVALID_HANDLE_VALUE
, file2
= INVALID_HANDLE_VALUE
;
472 BY_HANDLE_FILE_INFORMATION info1
, info2
;
474 file1
= CreateFileW(name1
, 0, FILE_SHARE_DELETE
| FILE_SHARE_READ
| FILE_SHARE_WRITE
, 0, OPEN_EXISTING
, 0, 0);
475 if (file1
== INVALID_HANDLE_VALUE
|| !GetFileInformationByHandle(file1
, &info1
))
478 file2
= CreateFileW(name2
, 0, FILE_SHARE_DELETE
| FILE_SHARE_READ
| FILE_SHARE_WRITE
, 0, OPEN_EXISTING
, 0, 0);
479 if (file2
== INVALID_HANDLE_VALUE
|| !GetFileInformationByHandle(file2
, &info2
))
482 ret
= info1
.dwVolumeSerialNumber
== info2
.dwVolumeSerialNumber
483 && info1
.nFileIndexHigh
== info2
.nFileIndexHigh
484 && info1
.nFileIndexLow
== info2
.nFileIndexLow
;
486 if (file1
!= INVALID_HANDLE_VALUE
)
488 if (file2
!= INVALID_HANDLE_VALUE
)
493 /****************************************************************************
497 * optionally reading only until EOF (ascii copy)
498 * optionally appending onto an existing file (append)
499 * Returns TRUE on success
501 static BOOL
WCMD_ManualCopy(WCHAR
*srcname
, WCHAR
*dstname
, BOOL ascii
, BOOL append
)
505 DWORD bytesread
, byteswritten
;
507 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
508 wine_dbgstr_w(srcname
), wine_dbgstr_w(dstname
), append
);
510 in
= CreateFileW(srcname
, GENERIC_READ
, 0, NULL
,
511 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
512 if (in
== INVALID_HANDLE_VALUE
) {
513 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname
), GetLastError());
517 /* Open the output file, overwriting if not appending */
518 out
= CreateFileW(dstname
, GENERIC_WRITE
, 0, NULL
,
519 append
?OPEN_EXISTING
:CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
520 if (out
== INVALID_HANDLE_VALUE
) {
521 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname
), GetLastError());
526 /* Move to end of destination if we are going to append to it */
528 SetFilePointer(out
, 0, NULL
, FILE_END
);
531 /* Loop copying data from source to destination until EOF read */
534 char buffer
[MAXSTRING
];
536 ok
= ReadFile(in
, buffer
, MAXSTRING
, &bytesread
, NULL
);
539 /* Stop at first EOF */
541 char *ptr
= (char *)memchr((void *)buffer
, '\x1a', bytesread
);
542 if (ptr
) bytesread
= (ptr
- buffer
);
546 ok
= WriteFile(out
, buffer
, bytesread
, &byteswritten
, NULL
);
547 if (!ok
|| byteswritten
!= bytesread
) {
548 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
549 wine_dbgstr_w(dstname
), GetLastError());
553 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
554 wine_dbgstr_w(srcname
), GetLastError());
556 } while (ok
&& bytesread
> 0);
563 /****************************************************************************
566 * Copy a file or wildcarded set.
567 * For ascii/binary type copies, it gets complex:
568 * Syntax on command line is
569 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
570 * Where first /a or /b sets 'mode in operation' until another is found
571 * once another is found, it applies to the file preceding the /a or /b
572 * In addition each filename can contain wildcards
573 * To make matters worse, the + may be in the same parameter (i.e. no
574 * whitespace) or with whitespace separating it
576 * ASCII mode on read == read and stop at first EOF
577 * ASCII mode on write == append EOF to destination
578 * Binary == copy as-is
580 * Design of this is to build up a list of files which will be copied into a
581 * list, then work through the list file by file.
582 * If no destination is specified, it defaults to the name of the first file in
583 * the list, but the current directory.
587 void WCMD_copy(WCHAR
* args
) {
589 BOOL opt_d
, opt_v
, opt_n
, opt_z
, opt_y
, opt_noty
;
594 HANDLE hff
= INVALID_HANDLE_VALUE
;
595 int binarymode
= -1; /* -1 means use the default, 1 is binary, 0 ascii */
596 BOOL concatnextfilename
= FALSE
; /* True if we have just processed a + */
597 BOOL anyconcats
= FALSE
; /* Have we found any + options */
598 BOOL appendfirstsource
= FALSE
; /* Use first found filename as destination */
599 BOOL writtenoneconcat
= FALSE
; /* Remember when the first concatenated file done */
600 BOOL prompt
; /* Prompt before overwriting */
601 WCHAR destname
[MAX_PATH
]; /* Used in calculating the destination name */
602 BOOL destisdirectory
= FALSE
; /* Is the destination a directory? */
606 BOOL dstisdevice
= FALSE
;
607 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
609 typedef struct _COPY_FILES
611 struct _COPY_FILES
*next
;
616 COPY_FILES
*sourcelist
= NULL
;
617 COPY_FILES
*lastcopyentry
= NULL
;
618 COPY_FILES
*destination
= NULL
;
619 COPY_FILES
*thiscopy
= NULL
;
620 COPY_FILES
*prevcopy
= NULL
;
622 /* Assume we were successful! */
625 /* If no args supplied at all, report an error */
626 if (param1
[0] == 0x00) {
627 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG
));
632 opt_d
= opt_v
= opt_n
= opt_z
= opt_y
= opt_noty
= FALSE
;
634 /* Walk through all args, building up a list of files to process */
635 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
636 while (*(thisparam
)) {
640 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam
));
642 /* Handle switches */
643 if (*thisparam
== '/') {
644 while (*thisparam
== '/') {
646 if (towupper(*thisparam
) == 'D') {
648 if (opt_d
) WINE_FIXME("copy /D support not implemented yet\n");
649 } else if (towupper(*thisparam
) == 'Y') {
651 } else if (towupper(*thisparam
) == '-' && towupper(*(thisparam
+1)) == 'Y') {
653 } else if (towupper(*thisparam
) == 'V') {
655 if (opt_v
) WINE_FIXME("copy /V support not implemented yet\n");
656 } else if (towupper(*thisparam
) == 'N') {
658 if (opt_n
) WINE_FIXME("copy /N support not implemented yet\n");
659 } else if (towupper(*thisparam
) == 'Z') {
661 if (opt_z
) WINE_FIXME("copy /Z support not implemented yet\n");
662 } else if (towupper(*thisparam
) == 'A') {
663 if (binarymode
!= 0) {
665 WINE_TRACE("Subsequent files will be handled as ASCII\n");
666 if (destination
!= NULL
) {
667 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination
->name
));
668 destination
->binarycopy
= binarymode
;
669 } else if (lastcopyentry
!= NULL
) {
670 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry
->name
));
671 lastcopyentry
->binarycopy
= binarymode
;
674 } else if (towupper(*thisparam
) == 'B') {
675 if (binarymode
!= 1) {
677 WINE_TRACE("Subsequent files will be handled as binary\n");
678 if (destination
!= NULL
) {
679 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination
->name
));
680 destination
->binarycopy
= binarymode
;
681 } else if (lastcopyentry
!= NULL
) {
682 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry
->name
));
683 lastcopyentry
->binarycopy
= binarymode
;
687 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam
));
692 /* This parameter was purely switches, get the next one */
693 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
697 /* We have found something which is not a switch. If could be anything of the form
698 sourcefilename (which could be destination too)
699 + (when filename + filename syntex used)
700 sourcefilename+sourcefilename
702 +/b[tests show windows then ignores to end of parameter]
705 if (*thisparam
=='+') {
706 if (lastcopyentry
== NULL
) {
707 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
711 concatnextfilename
= TRUE
;
715 /* Move to next thing to process */
717 if (*thisparam
== 0x00)
718 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
722 /* We have found something to process - build a COPY_FILE block to store it */
723 thiscopy
= heap_xalloc(sizeof(COPY_FILES
));
725 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam
));
726 thiscopy
->concatenate
= concatnextfilename
;
727 thiscopy
->binarycopy
= binarymode
;
728 thiscopy
->next
= NULL
;
730 /* Time to work out the name. Allocate at least enough space (deliberately too much to
731 leave space to append \* to the end) , then copy in character by character. Strip off
732 quotes if we find them. */
733 len
= lstrlenW(thisparam
) + (sizeof(WCHAR
) * 5); /* 5 spare characters, null + \*.* */
734 thiscopy
->name
= heap_xalloc(len
*sizeof(WCHAR
));
735 memset(thiscopy
->name
, 0x00, len
);
738 pos2
= thiscopy
->name
;
740 while (*pos1
&& (inquotes
|| (*pos1
!= '+' && *pos1
!= '/'))) {
742 inquotes
= !inquotes
;
744 } else *pos2
++ = *pos1
++;
747 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy
->name
));
749 /* This is either the first source, concatenated subsequent source or destination */
750 if (sourcelist
== NULL
) {
751 WINE_TRACE("Adding as first source part\n");
752 sourcelist
= thiscopy
;
753 lastcopyentry
= thiscopy
;
754 } else if (concatnextfilename
) {
755 WINE_TRACE("Adding to source file list to be concatenated\n");
756 lastcopyentry
->next
= thiscopy
;
757 lastcopyentry
= thiscopy
;
758 } else if (destination
== NULL
) {
759 destination
= thiscopy
;
761 /* We have processed sources and destinations and still found more to do - invalid */
762 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
766 concatnextfilename
= FALSE
;
768 /* We either need to process the rest of the parameter or move to the next */
769 if (*pos1
== '/' || *pos1
== '+') {
773 thisparam
= WCMD_parameter(args
, argno
++, &rawarg
, TRUE
, FALSE
);
777 /* Ensure we have at least one source file */
779 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
784 /* Default whether automatic overwriting is on. If we are interactive then
785 we prompt by default, otherwise we overwrite by default
786 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
787 if (opt_noty
) prompt
= TRUE
;
788 else if (opt_y
) prompt
= FALSE
;
790 /* By default, we will force the overwrite in batch mode and ask for
791 * confirmation in interactive mode. */
792 prompt
= interactive
;
793 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
794 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
795 * default behavior. */
796 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, ARRAY_SIZE(copycmd
));
797 if (len
&& len
< ARRAY_SIZE(copycmd
)) {
798 if (!lstrcmpiW (copycmd
, parmY
))
800 else if (!lstrcmpiW (copycmd
, parmNoY
))
805 /* Calculate the destination now - if none supplied, it's current dir +
806 filename of first file in list*/
807 if (destination
== NULL
) {
809 WINE_TRACE("No destination supplied, so need to calculate it\n");
810 lstrcpyW(destname
, dotW
);
811 lstrcatW(destname
, slashW
);
813 destination
= heap_xalloc(sizeof(COPY_FILES
));
814 if (destination
== NULL
) goto exitreturn
;
815 destination
->concatenate
= FALSE
; /* Not used for destination */
816 destination
->binarycopy
= binarymode
;
817 destination
->next
= NULL
; /* Not used for destination */
818 destination
->name
= NULL
; /* To be filled in */
819 destisdirectory
= TRUE
;
825 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
827 /* Convert to fully qualified path/filename */
828 GetFullPathNameW(destination
->name
, ARRAY_SIZE(destname
), destname
, &filenamepart
);
829 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname
));
831 /* If parameter is a directory, ensure it ends in \ */
832 attributes
= GetFileAttributesW(destname
);
833 if (ends_with_backslash( destname
) ||
834 ((attributes
!= INVALID_FILE_ATTRIBUTES
) &&
835 (attributes
& FILE_ATTRIBUTE_DIRECTORY
))) {
837 destisdirectory
= TRUE
;
838 if (!ends_with_backslash( destname
)) lstrcatW(destname
, slashW
);
839 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname
));
843 /* Normally, the destination is the current directory unless we are
844 concatenating, in which case it's current directory plus first filename.
846 In addition by default it is a binary copy unless concatenating, when
847 the copy defaults to an ascii copy (stop at EOF). We do not know the
848 first source part yet (until we search) so flag as needing filling in. */
851 /* We have found an a+b type syntax, so destination has to be a filename
852 and we need to default to ascii copying. If we have been supplied a
853 directory as the destination, we need to defer calculating the name */
854 if (destisdirectory
) appendfirstsource
= TRUE
;
855 if (destination
->binarycopy
== -1) destination
->binarycopy
= 0;
857 } else if (!destisdirectory
) {
858 /* We have been asked to copy to a filename. Default to ascii IF the
859 source contains wildcards (true even if only one match) */
860 if (wcspbrk(sourcelist
->name
, wildcardsW
) != NULL
) {
861 anyconcats
= TRUE
; /* We really are concatenating to a single file */
862 if (destination
->binarycopy
== -1) {
863 destination
->binarycopy
= 0;
866 if (destination
->binarycopy
== -1) {
867 destination
->binarycopy
= 1;
872 /* Save away the destination name*/
873 heap_free(destination
->name
);
874 destination
->name
= heap_strdupW(destname
);
875 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
876 wine_dbgstr_w(destname
), appendfirstsource
);
878 /* Remember if the destination is a device */
879 if (wcsncmp(destination
->name
, deviceW
, lstrlenW(deviceW
)) == 0) {
880 WINE_TRACE("Destination is a device\n");
884 /* Now we need to walk the set of sources, and process each name we come to.
885 If anyconcats is true, we are writing to one file, otherwise we are using
886 the source name each time.
887 If destination exists, prompt for overwrite the first time (if concatenating
888 we ask each time until yes is answered)
889 The first source file we come across must exist (when wildcards expanded)
890 and if concatenating with overwrite prompts, each source file must exist
891 until a yes is answered. */
893 thiscopy
= sourcelist
;
896 while (thiscopy
!= NULL
) {
898 WCHAR srcpath
[MAX_PATH
];
899 const WCHAR
*srcname
;
902 BOOL srcisdevice
= FALSE
;
904 /* If it was not explicit, we now know whether we are concatenating or not and
905 hence whether to copy as binary or ascii */
906 if (thiscopy
->binarycopy
== -1) thiscopy
->binarycopy
= !anyconcats
;
908 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
909 to where the filename portion begins (used for wildcard expansion). */
910 GetFullPathNameW(thiscopy
->name
, ARRAY_SIZE(srcpath
), srcpath
, &filenamepart
);
911 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath
));
913 /* If parameter is a directory, ensure it ends in \* */
914 attributes
= GetFileAttributesW(srcpath
);
915 if (ends_with_backslash( srcpath
)) {
917 /* We need to know where the filename part starts, so append * and
918 recalculate the full resulting path */
919 lstrcatW(thiscopy
->name
, starW
);
920 GetFullPathNameW(thiscopy
->name
, ARRAY_SIZE(srcpath
), srcpath
, &filenamepart
);
921 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath
));
923 } else if ((wcspbrk(srcpath
, wildcardsW
) == NULL
) &&
924 (attributes
!= INVALID_FILE_ATTRIBUTES
) &&
925 (attributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
927 /* We need to know where the filename part starts, so append \* and
928 recalculate the full resulting path */
929 lstrcatW(thiscopy
->name
, slashstarW
);
930 GetFullPathNameW(thiscopy
->name
, ARRAY_SIZE(srcpath
), srcpath
, &filenamepart
);
931 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath
));
934 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
935 wine_dbgstr_w(srcpath
), anyconcats
);
937 /* If the source is a device, just use it, otherwise search */
938 if (wcsncmp(srcpath
, deviceW
, lstrlenW(deviceW
)) == 0) {
939 WINE_TRACE("Source is a device\n");
941 srcname
= &srcpath
[4]; /* After the \\.\ prefix */
944 /* Loop through all source files */
945 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath
));
946 hff
= FindFirstFileW(srcpath
, &fd
);
947 if (hff
!= INVALID_HANDLE_VALUE
) {
948 srcname
= fd
.cFileName
;
952 if (srcisdevice
|| hff
!= INVALID_HANDLE_VALUE
) {
954 WCHAR outname
[MAX_PATH
];
956 BOOL appendtofirstfile
= FALSE
;
958 /* Skip . and .., and directories */
959 if (!srcisdevice
&& fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
960 WINE_TRACE("Skipping directories\n");
963 /* Build final destination name */
964 lstrcpyW(outname
, destination
->name
);
965 if (destisdirectory
|| appendfirstsource
) lstrcatW(outname
, srcname
);
967 /* Build source name */
968 if (!srcisdevice
) lstrcpyW(filenamepart
, srcname
);
970 /* Do we just overwrite (we do if we are writing to a device) */
972 if (dstisdevice
|| (anyconcats
&& writtenoneconcat
)) {
976 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath
));
977 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname
));
978 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
979 thiscopy
->binarycopy
, destination
->binarycopy
, overwrite
, prompt
);
981 if (!writtenoneconcat
) {
982 appendtofirstfile
= anyconcats
&& WCMD_IsSameFile(srcpath
, outname
);
985 /* Prompt before overwriting */
986 if (appendtofirstfile
) {
988 } else if (!overwrite
) {
989 DWORD attributes
= GetFileAttributesW(outname
);
990 if (attributes
!= INVALID_FILE_ATTRIBUTES
) {
992 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), outname
);
993 overwrite
= WCMD_ask_confirm(question
, FALSE
, NULL
);
996 else overwrite
= TRUE
;
999 /* If we needed to save away the first filename, do it */
1000 if (appendfirstsource
&& overwrite
) {
1001 heap_free(destination
->name
);
1002 destination
->name
= heap_strdupW(outname
);
1003 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname
));
1004 appendfirstsource
= FALSE
;
1005 destisdirectory
= FALSE
;
1008 /* Do the copy as appropriate */
1010 if (anyconcats
&& WCMD_IsSameFile(srcpath
, outname
)) {
1011 /* Silently skip if the destination file is also a source file */
1013 } else if (anyconcats
&& writtenoneconcat
) {
1014 if (thiscopy
->binarycopy
) {
1015 status
= WCMD_ManualCopy(srcpath
, outname
, FALSE
, TRUE
);
1017 status
= WCMD_ManualCopy(srcpath
, outname
, TRUE
, TRUE
);
1019 } else if (!thiscopy
->binarycopy
) {
1020 status
= WCMD_ManualCopy(srcpath
, outname
, TRUE
, FALSE
);
1021 } else if (srcisdevice
) {
1022 status
= WCMD_ManualCopy(srcpath
, outname
, FALSE
, FALSE
);
1024 status
= CopyFileW(srcpath
, outname
, FALSE
);
1027 WCMD_print_error ();
1030 WINE_TRACE("Copied successfully\n");
1031 if (anyconcats
) writtenoneconcat
= TRUE
;
1033 /* Append EOF if ascii destination and we are not going to add more onto the end
1034 Note: Testing shows windows has an optimization whereas if you have a binary
1035 copy of a file to a single destination (ie concatenation) then it does not add
1036 the EOF, hence the check on the source copy type below. */
1037 if (!destination
->binarycopy
&& !anyconcats
&& !thiscopy
->binarycopy
) {
1038 if (!WCMD_AppendEOF(outname
)) {
1039 WCMD_print_error ();
1046 } while (!srcisdevice
&& FindNextFileW(hff
, &fd
) != 0);
1047 if (!srcisdevice
) FindClose (hff
);
1049 /* Error if the first file was not found */
1050 if (!anyconcats
|| !writtenoneconcat
) {
1051 WCMD_print_error ();
1056 /* Step on to the next supplied source */
1057 thiscopy
= thiscopy
-> next
;
1060 /* Append EOF if ascii destination and we were concatenating */
1061 if (!errorlevel
&& !destination
->binarycopy
&& anyconcats
&& writtenoneconcat
) {
1062 if (!WCMD_AppendEOF(destination
->name
)) {
1063 WCMD_print_error ();
1068 /* Exit out of the routine, freeing any remaining allocated memory */
1071 thiscopy
= sourcelist
;
1072 while (thiscopy
!= NULL
) {
1073 prevcopy
= thiscopy
;
1074 /* Free up this block*/
1075 thiscopy
= thiscopy
-> next
;
1076 heap_free(prevcopy
->name
);
1077 heap_free(prevcopy
);
1080 /* Free up the destination memory */
1082 heap_free(destination
->name
);
1083 heap_free(destination
);
1089 /****************************************************************************
1092 * Create a directory (and, if needed, any intermediate directories).
1094 * Modifies its argument by replacing slashes temporarily with nulls.
1097 static BOOL
create_full_path(WCHAR
* path
)
1101 /* don't mess with drive letter portion of path, if any */
1106 /* Strip trailing slashes. */
1107 for (p
= path
+ lstrlenW(path
) - 1; p
!= start
&& *p
== '\\'; p
--)
1110 /* Step through path, creating intermediate directories as needed. */
1111 /* First component includes drive letter, if any. */
1115 /* Skip to end of component */
1116 while (*p
== '\\') p
++;
1117 while (*p
&& *p
!= '\\') p
++;
1119 /* path is now the original full path */
1120 return CreateDirectoryW(path
, NULL
);
1122 /* Truncate path, create intermediate directory, and restore path */
1124 rv
= CreateDirectoryW(path
, NULL
);
1126 if (!rv
&& GetLastError() != ERROR_ALREADY_EXISTS
)
1133 void WCMD_create_dir (WCHAR
*args
) {
1137 if (param1
[0] == 0x00) {
1138 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1141 /* Loop through all args */
1143 WCHAR
*thisArg
= WCMD_parameter(args
, argno
++, &argN
, FALSE
, FALSE
);
1145 if (!create_full_path(thisArg
)) {
1146 WCMD_print_error ();
1152 /* Parse the /A options given by the user on the commandline
1153 * into a bitmask of wanted attributes (*wantSet),
1154 * and a bitmask of unwanted attributes (*wantClear).
1156 static void WCMD_delete_parse_attributes(DWORD
*wantSet
, DWORD
*wantClear
) {
1157 static const WCHAR parmA
[] = {'/','A','\0'};
1160 /* both are strictly 'out' parameters */
1164 /* For each /A argument */
1165 for (p
=wcsstr(quals
, parmA
); p
!= NULL
; p
=wcsstr(p
, parmA
)) {
1166 /* Skip /A itself */
1169 /* Skip optional : */
1172 /* For each of the attribute specifier chars to this /A option */
1173 for (; *p
!= 0 && *p
!= '/'; p
++) {
1174 BOOL negate
= FALSE
;
1182 /* Convert the attribute specifier to a bit in one of the masks */
1184 case 'R': mask
= FILE_ATTRIBUTE_READONLY
; break;
1185 case 'H': mask
= FILE_ATTRIBUTE_HIDDEN
; break;
1186 case 'S': mask
= FILE_ATTRIBUTE_SYSTEM
; break;
1187 case 'A': mask
= FILE_ATTRIBUTE_ARCHIVE
; break;
1189 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
1199 /* If filename part of parameter is * or *.*,
1200 * and neither /Q nor /P options were given,
1201 * prompt the user whether to proceed.
1202 * Returns FALSE if user says no, TRUE otherwise.
1203 * *pPrompted is set to TRUE if the user is prompted.
1204 * (If /P supplied, del will prompt for individual files later.)
1206 static BOOL
WCMD_delete_confirm_wildcard(const WCHAR
*filename
, BOOL
*pPrompted
) {
1207 static const WCHAR parmP
[] = {'/','P','\0'};
1208 static const WCHAR parmQ
[] = {'/','Q','\0'};
1210 if ((wcsstr(quals
, parmQ
) == NULL
) && (wcsstr(quals
, parmP
) == NULL
)) {
1211 static const WCHAR anyExt
[]= {'.','*','\0'};
1213 WCHAR dir
[MAX_PATH
];
1214 WCHAR fname
[MAX_PATH
];
1215 WCHAR ext
[MAX_PATH
];
1216 WCHAR fpath
[MAX_PATH
];
1218 /* Convert path into actual directory spec */
1219 GetFullPathNameW(filename
, ARRAY_SIZE(fpath
), fpath
, NULL
);
1220 _wsplitpath(fpath
, drive
, dir
, fname
, ext
);
1222 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1223 if ((lstrcmpW(fname
, starW
) == 0) &&
1224 (*ext
== 0x00 || (lstrcmpW(ext
, anyExt
) == 0))) {
1226 WCHAR question
[MAXSTRING
];
1227 static const WCHAR fmt
[] = {'%','s',' ','\0'};
1229 /* Caller uses this to suppress "file not found" warning later */
1232 /* Ask for confirmation */
1233 wsprintfW(question
, fmt
, fpath
);
1234 return WCMD_ask_confirm(question
, TRUE
, NULL
);
1237 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1241 /* Helper function for WCMD_delete().
1242 * Deletes a single file, directory, or wildcard.
1243 * If /S was given, does it recursively.
1244 * Returns TRUE if a file was deleted.
1246 static BOOL
WCMD_delete_one (const WCHAR
*thisArg
) {
1248 static const WCHAR parmP
[] = {'/','P','\0'};
1249 static const WCHAR parmS
[] = {'/','S','\0'};
1250 static const WCHAR parmF
[] = {'/','F','\0'};
1252 DWORD unwanted_attrs
;
1254 WCHAR argCopy
[MAX_PATH
];
1255 WIN32_FIND_DATAW fd
;
1257 WCHAR fpath
[MAX_PATH
];
1259 BOOL handleParm
= TRUE
;
1261 WCMD_delete_parse_attributes(&wanted_attrs
, &unwanted_attrs
);
1263 lstrcpyW(argCopy
, thisArg
);
1264 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1265 wine_dbgstr_w(argCopy
), wine_dbgstr_w(quals
));
1267 if (!WCMD_delete_confirm_wildcard(argCopy
, &found
)) {
1268 /* Skip this arg if user declines to delete *.* */
1272 /* First, try to delete in the current directory */
1273 hff
= FindFirstFileW(argCopy
, &fd
);
1274 if (hff
== INVALID_HANDLE_VALUE
) {
1280 /* Support del <dirname> by just deleting all files dirname\* */
1282 && (wcschr(argCopy
,'*') == NULL
)
1283 && (wcschr(argCopy
,'?') == NULL
)
1284 && (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
1286 WCHAR modifiedParm
[MAX_PATH
];
1287 static const WCHAR slashStar
[] = {'\\','*','\0'};
1289 lstrcpyW(modifiedParm
, argCopy
);
1290 lstrcatW(modifiedParm
, slashStar
);
1293 WCMD_delete_one(modifiedParm
);
1295 } else if (handleParm
) {
1297 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1298 lstrcpyW (fpath
, argCopy
);
1300 p
= wcsrchr (fpath
, '\\');
1303 lstrcatW (fpath
, fd
.cFileName
);
1305 else lstrcpyW (fpath
, fd
.cFileName
);
1306 if (!(fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)) {
1309 /* Handle attribute matching (/A) */
1310 ok
= ((fd
.dwFileAttributes
& wanted_attrs
) == wanted_attrs
)
1311 && ((fd
.dwFileAttributes
& unwanted_attrs
) == 0);
1313 /* /P means prompt for each file */
1314 if (ok
&& wcsstr (quals
, parmP
) != NULL
) {
1317 /* Ask for confirmation */
1318 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT
), fpath
);
1319 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
1320 LocalFree(question
);
1323 /* Only proceed if ok to */
1326 /* If file is read only, and /A:r or /F supplied, delete it */
1327 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
&&
1328 ((wanted_attrs
& FILE_ATTRIBUTE_READONLY
) ||
1329 wcsstr (quals
, parmF
) != NULL
)) {
1330 SetFileAttributesW(fpath
, fd
.dwFileAttributes
& ~FILE_ATTRIBUTE_READONLY
);
1333 /* Now do the delete */
1334 if (!DeleteFileW(fpath
)) WCMD_print_error ();
1338 } while (FindNextFileW(hff
, &fd
) != 0);
1342 /* Now recurse into all subdirectories handling the parameter in the same way */
1343 if (wcsstr (quals
, parmS
) != NULL
) {
1345 WCHAR thisDir
[MAX_PATH
];
1349 WCHAR dir
[MAX_PATH
];
1350 WCHAR fname
[MAX_PATH
];
1351 WCHAR ext
[MAX_PATH
];
1353 /* Convert path into actual directory spec */
1354 GetFullPathNameW(argCopy
, ARRAY_SIZE(thisDir
), thisDir
, NULL
);
1355 _wsplitpath(thisDir
, drive
, dir
, fname
, ext
);
1357 lstrcpyW(thisDir
, drive
);
1358 lstrcatW(thisDir
, dir
);
1359 cPos
= lstrlenW(thisDir
);
1361 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir
));
1363 /* Append '*' to the directory */
1364 thisDir
[cPos
] = '*';
1365 thisDir
[cPos
+1] = 0x00;
1367 hff
= FindFirstFileW(thisDir
, &fd
);
1369 /* Remove residual '*' */
1370 thisDir
[cPos
] = 0x00;
1372 if (hff
!= INVALID_HANDLE_VALUE
) {
1373 DIRECTORY_STACK
*allDirs
= NULL
;
1374 DIRECTORY_STACK
*lastEntry
= NULL
;
1377 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
1378 (lstrcmpW(fd
.cFileName
, dotdotW
) != 0) &&
1379 (lstrcmpW(fd
.cFileName
, dotW
) != 0)) {
1381 DIRECTORY_STACK
*nextDir
;
1382 WCHAR subParm
[MAX_PATH
];
1384 /* Work out search parameter in sub dir */
1385 lstrcpyW (subParm
, thisDir
);
1386 lstrcatW (subParm
, fd
.cFileName
);
1387 lstrcatW (subParm
, slashW
);
1388 lstrcatW (subParm
, fname
);
1389 lstrcatW (subParm
, ext
);
1390 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm
));
1392 /* Allocate memory, add to list */
1393 nextDir
= heap_xalloc(sizeof(DIRECTORY_STACK
));
1394 if (allDirs
== NULL
) allDirs
= nextDir
;
1395 if (lastEntry
!= NULL
) lastEntry
->next
= nextDir
;
1396 lastEntry
= nextDir
;
1397 nextDir
->next
= NULL
;
1398 nextDir
->dirName
= heap_strdupW(subParm
);
1400 } while (FindNextFileW(hff
, &fd
) != 0);
1403 /* Go through each subdir doing the delete */
1404 while (allDirs
!= NULL
) {
1405 DIRECTORY_STACK
*tempDir
;
1407 tempDir
= allDirs
->next
;
1408 found
|= WCMD_delete_one (allDirs
->dirName
);
1410 heap_free(allDirs
->dirName
);
1420 /****************************************************************************
1423 * Delete a file or wildcarded set.
1426 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1427 * - Each set is a pattern, eg /ahr /as-r means
1428 * readonly+hidden OR nonreadonly system files
1429 * - The '-' applies to a single field, ie /a:-hr means read only
1433 BOOL
WCMD_delete (WCHAR
*args
) {
1436 BOOL argsProcessed
= FALSE
;
1437 BOOL foundAny
= FALSE
;
1441 for (argno
=0; ; argno
++) {
1446 thisArg
= WCMD_parameter (args
, argno
, &argN
, FALSE
, FALSE
);
1448 break; /* no more parameters */
1450 continue; /* skip options */
1452 argsProcessed
= TRUE
;
1453 found
= WCMD_delete_one(thisArg
);
1455 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND
), thisArg
);
1459 /* Handle no valid args */
1461 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
1469 * Returns a trimmed version of s with all leading and trailing whitespace removed
1473 static WCHAR
*WCMD_strtrim(const WCHAR
*s
)
1475 DWORD len
= lstrlenW(s
);
1476 const WCHAR
*start
= s
;
1479 result
= heap_xalloc((len
+ 1) * sizeof(WCHAR
));
1481 while (iswspace(*start
)) start
++;
1483 const WCHAR
*end
= s
+ len
- 1;
1484 while (end
> start
&& iswspace(*end
)) end
--;
1485 memcpy(result
, start
, (end
- start
+ 2) * sizeof(WCHAR
));
1486 result
[end
- start
+ 1] = '\0';
1494 /****************************************************************************
1497 * Echo input to the screen (or not). We don't try to emulate the bugs
1498 * in DOS (try typing "ECHO ON AGAIN" for an example).
1501 void WCMD_echo (const WCHAR
*args
)
1504 const WCHAR
*origcommand
= args
;
1507 if ( args
[0]==' ' || args
[0]=='\t' || args
[0]=='.'
1508 || args
[0]==':' || args
[0]==';' || args
[0]=='/')
1511 trimmed
= WCMD_strtrim(args
);
1512 if (!trimmed
) return;
1514 count
= lstrlenW(trimmed
);
1515 if (count
== 0 && origcommand
[0]!='.' && origcommand
[0]!=':'
1516 && origcommand
[0]!=';' && origcommand
[0]!='/') {
1517 if (echo_mode
) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), onW
);
1518 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT
), offW
);
1523 if (lstrcmpiW(trimmed
, onW
) == 0)
1525 else if (lstrcmpiW(trimmed
, offW
) == 0)
1528 WCMD_output_asis (args
);
1529 WCMD_output_asis (newlineW
);
1534 /*****************************************************************************
1537 * Execute a command, and any && or bracketed follow on to the command. The
1538 * first command to be executed may not be at the front of the
1539 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1541 static void WCMD_part_execute(CMD_LIST
**cmdList
, const WCHAR
*firstcmd
,
1542 BOOL isIF
, BOOL executecmds
)
1544 CMD_LIST
*curPosition
= *cmdList
;
1545 int myDepth
= (*cmdList
)->bracketDepth
;
1547 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d), isIF(%d)\n", cmdList
,
1548 wine_dbgstr_w(firstcmd
), executecmds
, isIF
);
1550 /* Skip leading whitespace between condition and the command */
1551 while (firstcmd
&& *firstcmd
&& (*firstcmd
==' ' || *firstcmd
=='\t')) firstcmd
++;
1553 /* Process the first command, if there is one */
1554 if (executecmds
&& firstcmd
&& *firstcmd
) {
1555 WCHAR
*command
= heap_strdupW(firstcmd
);
1556 WCMD_execute (firstcmd
, (*cmdList
)->redirects
, cmdList
, FALSE
);
1561 /* If it didn't move the position, step to next command */
1562 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1564 /* Process any other parts of the command */
1566 BOOL processThese
= executecmds
;
1569 static const WCHAR ifElse
[] = {'e','l','s','e'};
1571 /* execute all appropriate commands */
1572 curPosition
= *cmdList
;
1574 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d) processThese(%d)\n",
1576 (*cmdList
)->prevDelim
,
1577 (*cmdList
)->bracketDepth
,
1581 /* Execute any statements appended to the line */
1582 /* FIXME: Only if previous call worked for && or failed for || */
1583 if ((*cmdList
)->prevDelim
== CMD_ONFAILURE
||
1584 (*cmdList
)->prevDelim
== CMD_ONSUCCESS
) {
1585 if (processThese
&& (*cmdList
)->command
) {
1586 WCMD_execute ((*cmdList
)->command
, (*cmdList
)->redirects
,
1589 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1591 /* Execute any appended to the statement with (...) */
1592 } else if ((*cmdList
)->bracketDepth
> myDepth
) {
1594 *cmdList
= WCMD_process_commands(*cmdList
, TRUE
, FALSE
);
1596 WINE_TRACE("Skipping command %p due to stack depth\n", *cmdList
);
1598 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1600 /* End of the command - does 'ELSE ' follow as the next command? */
1602 if (isIF
&& WCMD_keyword_ws_found(ifElse
, ARRAY_SIZE(ifElse
), (*cmdList
)->command
)) {
1603 /* Swap between if and else processing */
1604 processThese
= !executecmds
;
1606 /* Process the ELSE part */
1608 const int keyw_len
= ARRAY_SIZE(ifElse
) + 1;
1609 WCHAR
*cmd
= ((*cmdList
)->command
) + keyw_len
;
1611 /* Skip leading whitespace between condition and the command */
1612 while (*cmd
&& (*cmd
==' ' || *cmd
=='\t')) cmd
++;
1614 WCMD_execute (cmd
, (*cmdList
)->redirects
, cmdList
, FALSE
);
1617 /* Loop skipping all commands until we get back to the current
1618 depth, including skipping commands and their subsequent
1619 pipes (eg cmd | prog) */
1621 *cmdList
= (*cmdList
)->nextcommand
;
1622 } while (*cmdList
&&
1623 ((*cmdList
)->bracketDepth
> myDepth
||
1624 (*cmdList
)->prevDelim
));
1626 /* After the else is complete, we need to now process subsequent commands */
1627 processThese
= TRUE
;
1629 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1631 /* If we were in an IF statement and we didn't find an else and yet we get back to
1632 the same bracket depth as the IF, then the IF statement is over. This is required
1633 to handle nested ifs properly */
1634 } else if (isIF
&& (*cmdList
)->bracketDepth
== myDepth
) {
1635 WINE_TRACE("Found end of this nested IF statement, ending this if\n");
1637 } else if (!processThese
) {
1638 if (curPosition
== *cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
1639 WINE_TRACE("Skipping this command, as in not process mode (next = %p)\n", *cmdList
);
1641 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList
);
1650 /*****************************************************************************
1651 * WCMD_parse_forf_options
1653 * Parses the for /f 'options', extracting the values and validating the
1654 * keywords. Note all keywords are optional.
1656 * options [I] The unparsed parameter string
1657 * eol [O] Set to the comment character (eol=x)
1658 * skip [O] Set to the number of lines to skip (skip=xx)
1659 * delims [O] Set to the token delimiters (delims=)
1660 * tokens [O] Set to the requested tokens, as provided (tokens=)
1661 * usebackq [O] Set to TRUE if usebackq found
1663 * Returns TRUE on success, FALSE on syntax error
1666 static BOOL
WCMD_parse_forf_options(WCHAR
*options
, WCHAR
*eol
, int *skip
,
1667 WCHAR
*delims
, WCHAR
*tokens
, BOOL
*usebackq
)
1670 WCHAR
*pos
= options
;
1671 int len
= lstrlenW(pos
);
1672 static const WCHAR eolW
[] = {'e','o','l','='};
1673 static const WCHAR skipW
[] = {'s','k','i','p','='};
1674 static const WCHAR tokensW
[] = {'t','o','k','e','n','s','='};
1675 static const WCHAR delimsW
[] = {'d','e','l','i','m','s','='};
1676 static const WCHAR usebackqW
[] = {'u','s','e','b','a','c','k','q'};
1677 static const WCHAR forf_defaultdelims
[] = {' ', '\t', '\0'};
1678 static const WCHAR forf_defaulttokens
[] = {'1', '\0'};
1680 /* Initialize to defaults */
1681 lstrcpyW(delims
, forf_defaultdelims
);
1682 lstrcpyW(tokens
, forf_defaulttokens
);
1687 /* Strip (optional) leading and trailing quotes */
1688 if ((*pos
== '"') && (pos
[len
-1] == '"')) {
1693 /* Process each keyword */
1694 while (pos
&& *pos
) {
1695 if (*pos
== ' ' || *pos
== '\t') {
1698 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1699 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1700 pos
, ARRAY_SIZE(eolW
), eolW
, ARRAY_SIZE(eolW
)) == CSTR_EQUAL
) {
1701 *eol
= *(pos
+ ARRAY_SIZE(eolW
));
1702 pos
= pos
+ ARRAY_SIZE(eolW
) + 1;
1703 WINE_TRACE("Found eol as %c(%x)\n", *eol
, *eol
);
1705 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1706 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1707 pos
, ARRAY_SIZE(skipW
), skipW
, ARRAY_SIZE(skipW
)) == CSTR_EQUAL
) {
1708 WCHAR
*nextchar
= NULL
;
1709 pos
= pos
+ ARRAY_SIZE(skipW
);
1710 *skip
= wcstoul(pos
, &nextchar
, 0);
1711 WINE_TRACE("Found skip as %d lines\n", *skip
);
1714 /* Save if usebackq semantics are in effect */
1715 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
, pos
,
1716 ARRAY_SIZE(usebackqW
), usebackqW
, ARRAY_SIZE(usebackqW
)) == CSTR_EQUAL
) {
1718 pos
= pos
+ ARRAY_SIZE(usebackqW
);
1719 WINE_TRACE("Found usebackq\n");
1721 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1722 if you finish the optionsroot string with delims= otherwise the space is
1723 just a token delimiter! */
1724 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1725 pos
, ARRAY_SIZE(delimsW
), delimsW
, ARRAY_SIZE(delimsW
)) == CSTR_EQUAL
) {
1728 pos
= pos
+ ARRAY_SIZE(delimsW
);
1729 while (*pos
&& *pos
!= ' ') {
1733 if (*pos
==' ' && *(pos
+1)==0) delims
[i
++] = *pos
;
1734 delims
[i
++] = 0; /* Null terminate the delims */
1735 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims
));
1737 /* Save the tokens being requested */
1738 } else if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
1739 pos
, ARRAY_SIZE(tokensW
), tokensW
, ARRAY_SIZE(tokensW
)) == CSTR_EQUAL
) {
1742 pos
= pos
+ ARRAY_SIZE(tokensW
);
1743 while (*pos
&& *pos
!= ' ') {
1747 tokens
[i
++] = 0; /* Null terminate the tokens */
1748 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens
));
1751 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos
));
1758 /*****************************************************************************
1759 * WCMD_add_dirstowalk
1761 * When recursing through directories (for /r), we need to add to the list of
1762 * directories still to walk, any subdirectories of the one we are processing.
1765 * options [I] The remaining list of directories still to process
1767 * Note this routine inserts the subdirectories found between the entry being
1768 * processed, and any other directory still to be processed, mimicking what
1771 static void WCMD_add_dirstowalk(DIRECTORY_STACK
*dirsToWalk
) {
1772 DIRECTORY_STACK
*remainingDirs
= dirsToWalk
;
1773 WCHAR fullitem
[MAX_PATH
];
1774 WIN32_FIND_DATAW fd
;
1777 /* Build a generic search and add all directories on the list of directories
1779 lstrcpyW(fullitem
, dirsToWalk
->dirName
);
1780 lstrcatW(fullitem
, slashstarW
);
1781 hff
= FindFirstFileW(fullitem
, &fd
);
1782 if (hff
!= INVALID_HANDLE_VALUE
) {
1784 WINE_TRACE("Looking for subdirectories\n");
1785 if ((fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) &&
1786 (lstrcmpW(fd
.cFileName
, dotdotW
) != 0) &&
1787 (lstrcmpW(fd
.cFileName
, dotW
) != 0))
1789 /* Allocate memory, add to list */
1790 DIRECTORY_STACK
*toWalk
= heap_xalloc(sizeof(DIRECTORY_STACK
));
1791 WINE_TRACE("(%p->%p)\n", remainingDirs
, remainingDirs
->next
);
1792 toWalk
->next
= remainingDirs
->next
;
1793 remainingDirs
->next
= toWalk
;
1794 remainingDirs
= toWalk
;
1795 toWalk
->dirName
= heap_xalloc(sizeof(WCHAR
) * (lstrlenW(dirsToWalk
->dirName
) + 2 + lstrlenW(fd
.cFileName
)));
1796 lstrcpyW(toWalk
->dirName
, dirsToWalk
->dirName
);
1797 lstrcatW(toWalk
->dirName
, slashW
);
1798 lstrcatW(toWalk
->dirName
, fd
.cFileName
);
1799 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk
->dirName
),
1800 toWalk
, toWalk
->next
);
1802 } while (FindNextFileW(hff
, &fd
) != 0);
1803 WINE_TRACE("Finished adding all subdirectories\n");
1808 /**************************************************************************
1809 * WCMD_for_nexttoken
1811 * Parse the token= line, identifying the next highest number not processed
1812 * so far. Count how many tokens are referred (including duplicates) and
1813 * optionally return that, plus optionally indicate if the tokens= line
1817 * lasttoken [I] - Identifies the token index of the last one
1818 * returned so far (-1 used for first loop)
1819 * tokenstr [I] - The specified tokens= line
1820 * firstCmd [O] - Optionally indicate how many tokens are listed
1821 * doAll [O] - Optionally indicate if line ends with *
1822 * duplicates [O] - Optionally indicate if there is any evidence of
1823 * overlaying tokens in the string
1824 * Note the caller should keep a running track of duplicates as the tokens
1825 * are recursively passed. If any have duplicates, then the * token should
1828 static int WCMD_for_nexttoken(int lasttoken
, WCHAR
*tokenstr
,
1829 int *totalfound
, BOOL
*doall
,
1832 WCHAR
*pos
= tokenstr
;
1835 if (totalfound
) *totalfound
= 0;
1836 if (doall
) *doall
= FALSE
;
1837 if (duplicates
) *duplicates
= FALSE
;
1839 WINE_TRACE("Find next token after %d in %s\n", lasttoken
,
1840 wine_dbgstr_w(tokenstr
));
1842 /* Loop through the token string, parsing it. Valid syntax is:
1843 token=m or x-y with comma delimiter and optionally * to finish*/
1845 int nextnumber1
, nextnumber2
= -1;
1848 /* Remember if the next character is a star, it indicates a need to
1849 show all remaining tokens and should be the last character */
1851 if (doall
) *doall
= TRUE
;
1852 if (totalfound
) (*totalfound
)++;
1853 /* If we have not found a next token to return, then indicate
1854 time to process the star */
1855 if (nexttoken
== -1) {
1856 if (lasttoken
== -1) {
1857 /* Special case the syntax of tokens=* which just means get whole line */
1860 nexttoken
= lasttoken
;
1866 /* Get the next number */
1867 nextnumber1
= wcstoul(pos
, &nextchar
, 10);
1869 /* If it is followed by a minus, it's a range, so get the next one as well */
1870 if (*nextchar
== '-') {
1871 nextnumber2
= wcstoul(nextchar
+1, &nextchar
, 10);
1873 /* We want to return the lowest number that is higher than lasttoken
1874 but only if range is positive */
1875 if (nextnumber2
>= nextnumber1
&&
1876 lasttoken
< nextnumber2
) {
1879 if (nexttoken
== -1) {
1880 nextvalue
= max(nextnumber1
, (lasttoken
+1));
1882 nextvalue
= min(nexttoken
, max(nextnumber1
, (lasttoken
+1)));
1885 /* Flag if duplicates identified */
1886 if (nexttoken
== nextvalue
&& duplicates
) *duplicates
= TRUE
;
1888 nexttoken
= nextvalue
;
1891 /* Update the running total for the whole range */
1892 if (nextnumber2
>= nextnumber1
&& totalfound
) {
1893 *totalfound
= *totalfound
+ 1 + (nextnumber2
- nextnumber1
);
1897 } else if (pos
!= nextchar
) {
1898 if (totalfound
) (*totalfound
)++;
1900 /* See if the number found is one we have already seen */
1901 if (nextnumber1
== nexttoken
&& duplicates
) *duplicates
= TRUE
;
1903 /* We want to return the lowest number that is higher than lasttoken */
1904 if (lasttoken
< nextnumber1
&&
1905 ((nexttoken
== -1) || (nextnumber1
< nexttoken
))) {
1906 nexttoken
= nextnumber1
;
1911 /* Step on to the next character, usually over comma */
1918 if (nexttoken
== -1) {
1919 WINE_TRACE("No next token found, previous was %d\n", lasttoken
);
1920 nexttoken
= lasttoken
;
1921 } else if (nexttoken
==lasttoken
&& doall
&& *doall
) {
1922 WINE_TRACE("Request for all remaining tokens now\n");
1924 WINE_TRACE("Found next token after %d was %d\n", lasttoken
, nexttoken
);
1926 if (totalfound
) WINE_TRACE("Found total tokens to be %d\n", *totalfound
);
1927 if (duplicates
&& *duplicates
) WINE_TRACE("Duplicate numbers found\n");
1931 /**************************************************************************
1934 * When parsing file or string contents (for /f), once the string to parse
1935 * has been identified, handle the various options and call the do part
1939 * cmdStart [I] - Identifies the list of commands making up the
1940 * for loop body (especially if brackets in use)
1941 * firstCmd [I] - The textual start of the command after the DO
1942 * which is within the first item of cmdStart
1943 * cmdEnd [O] - Identifies where to continue after the DO
1944 * variable [I] - The variable identified on the for line
1945 * buffer [I] - The string to parse
1946 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1947 * forf_skip [I/O] - How many lines to skip first
1948 * forf_eol [I] - The 'end of line' (comment) character
1949 * forf_delims [I] - The delimiters to use when breaking the string apart
1950 * forf_tokens [I] - The tokens to use when breaking the string apart
1952 static void WCMD_parse_line(CMD_LIST
*cmdStart
,
1953 const WCHAR
*firstCmd
,
1955 const WCHAR variable
,
1961 WCHAR
*forf_tokens
) {
1964 FOR_CONTEXT oldcontext
;
1965 int varidx
, varoffset
;
1966 int nexttoken
, lasttoken
= -1;
1967 BOOL starfound
= FALSE
;
1968 BOOL thisduplicate
= FALSE
;
1969 BOOL anyduplicates
= FALSE
;
1971 static WCHAR emptyW
[] = L
"";
1973 /* Skip lines if requested */
1979 /* Save away any existing for variable context (e.g. nested for loops) */
1980 oldcontext
= forloopcontext
;
1982 /* Extract the parameters based on the tokens= value (There will always
1983 be some value, as if it is not supplied, it defaults to tokens=1).
1985 Count how many tokens are named in the line, identify the lowest
1986 Empty (set to null terminated string) that number of named variables
1987 While lasttoken != nextlowest
1988 %letter = parameter number 'nextlowest'
1989 letter++ (if >26 or >52 abort)
1990 Go through token= string finding next lowest number
1991 If token ends in * set %letter = raw position of token(nextnumber+1)
1994 nexttoken
= WCMD_for_nexttoken(lasttoken
, forf_tokens
, &totalfound
,
1995 &starfound
, &thisduplicate
);
1996 varidx
= FOR_VAR_IDX(variable
);
1998 /* Empty out variables */
2000 varidx
>= 0 && varoffset
<totalfound
&& (((varidx
%26) + varoffset
) < 26);
2002 forloopcontext
.variable
[varidx
+ varoffset
] = emptyW
;
2005 /* Loop extracting the tokens
2006 Note: nexttoken of 0 means there were no tokens requested, to handle
2007 the special case of tokens=* */
2009 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer
));
2010 while (varidx
>= 0 && (nexttoken
> 0 && (nexttoken
> lasttoken
))) {
2011 anyduplicates
|= thisduplicate
;
2013 /* Extract the token number requested and set into the next variable context */
2014 parm
= WCMD_parameter_with_delims(buffer
, (nexttoken
-1), NULL
, TRUE
, FALSE
, forf_delims
);
2015 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken
,
2016 varidx
+ varoffset
, wine_dbgstr_w(parm
));
2018 if (parm
) forloopcontext
.variable
[varidx
+ varoffset
] = heap_strdupW(parm
);
2020 if (((varidx
%26)+varoffset
) >= 26) break;
2023 /* Find the next token */
2024 lasttoken
= nexttoken
;
2025 nexttoken
= WCMD_for_nexttoken(lasttoken
, forf_tokens
, NULL
,
2026 &starfound
, &thisduplicate
);
2029 /* If all the rest of the tokens were requested, and there is still space in
2030 the variable range, write them now */
2031 if (!anyduplicates
&& starfound
&& varidx
>= 0 && (((varidx
%26) + varoffset
) < 26)) {
2033 WCMD_parameter_with_delims(buffer
, (nexttoken
-1), &parm
, FALSE
, FALSE
, forf_delims
);
2034 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2035 varidx
+ varoffset
, wine_dbgstr_w(parm
));
2036 if (parm
) forloopcontext
.variable
[varidx
+ varoffset
] = heap_strdupW(parm
);
2039 /* Execute the body of the foor loop with these values */
2040 if (forloopcontext
.variable
[varidx
] && forloopcontext
.variable
[varidx
][0] != forf_eol
) {
2041 CMD_LIST
*thisCmdStart
= cmdStart
;
2043 WCMD_part_execute(&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2044 *cmdEnd
= thisCmdStart
;
2047 /* Free the duplicated strings, and restore the context */
2050 for (i
=varidx
; i
<MAX_FOR_VARIABLES
; i
++) {
2051 if ((forloopcontext
.variable
[i
] != oldcontext
.variable
[i
]) &&
2052 (forloopcontext
.variable
[i
] != emptyW
)) {
2053 heap_free(forloopcontext
.variable
[i
]);
2058 /* Restore the original for variable contextx */
2059 forloopcontext
= oldcontext
;
2062 /**************************************************************************
2063 * WCMD_forf_getinputhandle
2065 * Return a file handle which can be used for reading the input lines,
2066 * either to a specific file (which may be quote delimited as we have to
2067 * read the parameters in raw mode) or to a command which we need to
2068 * execute. The command being executed runs in its own shell and stores
2069 * its data in a temporary file.
2072 * usebackq [I] - Indicates whether usebackq is in effect or not
2073 * itemStr [I] - The item to be handled, either a filename or
2074 * whole command string to execute
2075 * iscmd [I] - Identifies whether this is a command or not
2077 * Returns a file handle which can be used to read the input lines from.
2079 static HANDLE
WCMD_forf_getinputhandle(BOOL usebackq
, WCHAR
*itemstr
, BOOL iscmd
) {
2080 WCHAR temp_str
[MAX_PATH
];
2081 WCHAR temp_file
[MAX_PATH
];
2082 WCHAR temp_cmd
[MAXSTRING
];
2083 WCHAR
*trimmed
= NULL
;
2084 HANDLE hinput
= INVALID_HANDLE_VALUE
;
2085 static const WCHAR redirOutW
[] = {'>','%','s','\0'};
2086 static const WCHAR cmdW
[] = {'C','M','D','\0'};
2087 static const WCHAR cmdslashcW
[] = {'C','M','D','.','E','X','E',' ',
2088 '/','C',' ','%','s','\0'};
2090 /* Remove leading and trailing character (but there may be trailing whitespace too) */
2091 if ((iscmd
&& (itemstr
[0] == '`' && usebackq
)) ||
2092 (iscmd
&& (itemstr
[0] == '\'' && !usebackq
)) ||
2093 (!iscmd
&& (itemstr
[0] == '"' && usebackq
)))
2095 trimmed
= WCMD_strtrim(itemstr
);
2099 itemstr
[lstrlenW(itemstr
)-1] = 0x00;
2104 /* Get temp filename */
2105 GetTempPathW(ARRAY_SIZE(temp_str
), temp_str
);
2106 GetTempFileNameW(temp_str
, cmdW
, 0, temp_file
);
2108 /* Redirect output to the temporary file */
2109 wsprintfW(temp_str
, redirOutW
, temp_file
);
2110 wsprintfW(temp_cmd
, cmdslashcW
, itemstr
);
2111 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2112 wine_dbgstr_w(temp_cmd
), wine_dbgstr_w(temp_str
));
2113 WCMD_execute (temp_cmd
, temp_str
, NULL
, FALSE
);
2115 /* Open the file, read line by line and process */
2116 hinput
= CreateFileW(temp_file
, GENERIC_READ
, FILE_SHARE_READ
,
2117 NULL
, OPEN_EXISTING
, FILE_FLAG_DELETE_ON_CLOSE
, NULL
);
2120 /* Open the file, read line by line and process */
2121 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr
));
2122 hinput
= CreateFileW(itemstr
, GENERIC_READ
, FILE_SHARE_READ
,
2123 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
2129 /**************************************************************************
2132 * Batch file loop processing.
2134 * On entry: cmdList contains the syntax up to the set
2135 * next cmdList and all in that bracket contain the set data
2136 * next cmdlist contains the DO cmd
2137 * following that is either brackets or && entries (as per if)
2141 void WCMD_for (WCHAR
*p
, CMD_LIST
**cmdList
) {
2143 WIN32_FIND_DATAW fd
;
2146 static const WCHAR inW
[] = {'i','n'};
2147 static const WCHAR doW
[] = {'d','o'};
2148 CMD_LIST
*setStart
, *thisSet
, *cmdStart
, *cmdEnd
;
2151 WCHAR
*oldvariablevalue
;
2154 WCHAR optionsRoot
[MAX_PATH
];
2155 DIRECTORY_STACK
*dirsToWalk
= NULL
;
2156 BOOL expandDirs
= FALSE
;
2157 BOOL useNumbers
= FALSE
;
2158 BOOL doFileset
= FALSE
;
2159 BOOL doRecurse
= FALSE
;
2160 BOOL doExecuted
= FALSE
; /* Has the 'do' part been executed */
2161 LONG numbers
[3] = {0,0,0}; /* Defaults to 0 in native */
2163 CMD_LIST
*thisCmdStart
;
2164 int parameterNo
= 0;
2167 WCHAR forf_delims
[256];
2168 WCHAR forf_tokens
[MAXSTRING
];
2169 BOOL forf_usebackq
= FALSE
;
2171 /* Handle optional qualifiers (multiple are allowed) */
2172 WCHAR
*thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2175 while (thisArg
&& *thisArg
== '/') {
2176 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg
));
2178 switch (towupper(*thisArg
)) {
2179 case 'D': expandDirs
= TRUE
; break;
2180 case 'L': useNumbers
= TRUE
; break;
2182 /* Recursive is special case - /R can have an optional path following it */
2183 /* filenamesets are another special case - /F can have an optional options following it */
2187 /* When recursing directories, use current directory as the starting point unless
2188 subsequently overridden */
2189 doRecurse
= (towupper(*thisArg
) == 'R');
2190 if (doRecurse
) GetCurrentDirectoryW(ARRAY_SIZE(optionsRoot
), optionsRoot
);
2192 doFileset
= (towupper(*thisArg
) == 'F');
2194 /* Retrieve next parameter to see if is root/options (raw form required
2195 with for /f, or unquoted in for /r) */
2196 thisArg
= WCMD_parameter(p
, parameterNo
, NULL
, doFileset
, FALSE
);
2198 /* Next parm is either qualifier, path/options or variable -
2199 only care about it if it is the path/options */
2200 if (thisArg
&& *thisArg
!= '/' && *thisArg
!= '%') {
2202 lstrcpyW(optionsRoot
, thisArg
);
2207 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg
);
2210 /* Step to next token */
2211 thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2214 /* Ensure line continues with variable */
2215 if (*thisArg
!= '%') {
2216 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2220 /* With for /f parse the options if provided */
2222 if (!WCMD_parse_forf_options(optionsRoot
, &forf_eol
, &forf_skip
,
2223 forf_delims
, forf_tokens
, &forf_usebackq
))
2225 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2229 /* Set up the list of directories to recurse if we are going to */
2230 } else if (doRecurse
) {
2231 /* Allocate memory, add to list */
2232 dirsToWalk
= heap_xalloc(sizeof(DIRECTORY_STACK
));
2233 dirsToWalk
->next
= NULL
;
2234 dirsToWalk
->dirName
= heap_strdupW(optionsRoot
);
2235 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk
->dirName
));
2238 /* Variable should follow */
2239 lstrcpyW(variable
, thisArg
);
2240 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable
));
2241 varidx
= FOR_VAR_IDX(variable
[1]);
2243 /* Ensure line continues with IN */
2244 thisArg
= WCMD_parameter(p
, parameterNo
++, NULL
, FALSE
, FALSE
);
2246 || !(CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2247 thisArg
, ARRAY_SIZE(inW
), inW
, ARRAY_SIZE(inW
)) == CSTR_EQUAL
)) {
2248 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2252 /* Save away where the set of data starts and the variable */
2253 thisDepth
= (*cmdList
)->bracketDepth
;
2254 *cmdList
= (*cmdList
)->nextcommand
;
2255 setStart
= (*cmdList
);
2257 /* Skip until the close bracket */
2258 WINE_TRACE("Searching %p as the set\n", *cmdList
);
2260 (*cmdList
)->command
!= NULL
&&
2261 (*cmdList
)->bracketDepth
> thisDepth
) {
2262 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList
);
2263 *cmdList
= (*cmdList
)->nextcommand
;
2266 /* Skip the close bracket, if there is one */
2267 if (*cmdList
) *cmdList
= (*cmdList
)->nextcommand
;
2269 /* Syntax error if missing close bracket, or nothing following it
2270 and once we have the complete set, we expect a DO */
2271 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList
);
2272 if ((*cmdList
== NULL
) || !WCMD_keyword_ws_found(doW
, ARRAY_SIZE(doW
), (*cmdList
)->command
)) {
2273 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR
));
2279 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2280 mode, or once for the rest of the time. */
2283 /* Save away the starting position for the commands (and offset for the
2285 cmdStart
= *cmdList
;
2286 firstCmd
= (*cmdList
)->command
+ 3; /* Skip 'do ' */
2289 /* If we are recursing directories (ie /R), add all sub directories now, then
2290 prefix the root when searching for the item */
2291 if (dirsToWalk
) WCMD_add_dirstowalk(dirsToWalk
);
2294 /* Loop through all set entries */
2296 thisSet
->command
!= NULL
&&
2297 thisSet
->bracketDepth
>= thisDepth
) {
2299 /* Loop through all entries on the same line */
2302 WCHAR buffer
[MAXSTRING
];
2304 WINE_TRACE("Processing for set %p\n", thisSet
);
2306 while (*(staticitem
= WCMD_parameter (thisSet
->command
, i
, &itemStart
, TRUE
, FALSE
))) {
2309 * If the parameter within the set has a wildcard then search for matching files
2310 * otherwise do a literal substitution.
2312 static const WCHAR wildcards
[] = {'*','?','\0'};
2314 /* Take a copy of the item returned from WCMD_parameter as it is held in a
2315 static buffer which can be overwritten during parsing of the for body */
2316 WCHAR item
[MAXSTRING
];
2317 lstrcpyW(item
, staticitem
);
2319 thisCmdStart
= cmdStart
;
2322 WINE_TRACE("Processing for item %d '%s'\n", itemNum
, wine_dbgstr_w(item
));
2324 if (!useNumbers
&& !doFileset
) {
2325 WCHAR fullitem
[MAX_PATH
];
2328 /* Now build the item to use / search for in the specified directory,
2329 as it is fully qualified in the /R case */
2331 lstrcpyW(fullitem
, dirsToWalk
->dirName
);
2332 lstrcatW(fullitem
, slashW
);
2333 lstrcatW(fullitem
, item
);
2335 WCHAR
*prefix
= wcsrchr(item
, '\\');
2336 if (prefix
) prefixlen
= (prefix
- item
) + 1;
2337 lstrcpyW(fullitem
, item
);
2340 if (wcspbrk (fullitem
, wildcards
)) {
2341 hff
= FindFirstFileW(fullitem
, &fd
);
2342 if (hff
!= INVALID_HANDLE_VALUE
) {
2344 BOOL isDirectory
= FALSE
;
2346 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) isDirectory
= TRUE
;
2348 /* Handle as files or dirs appropriately, but ignore . and .. */
2349 if (isDirectory
== expandDirs
&&
2350 (lstrcmpW(fd
.cFileName
, dotdotW
) != 0) &&
2351 (lstrcmpW(fd
.cFileName
, dotW
) != 0))
2353 thisCmdStart
= cmdStart
;
2354 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd
.cFileName
));
2357 lstrcpyW(fullitem
, dirsToWalk
->dirName
);
2358 lstrcatW(fullitem
, slashW
);
2359 lstrcatW(fullitem
, fd
.cFileName
);
2361 if (prefixlen
) lstrcpynW(fullitem
, item
, prefixlen
+ 1);
2362 fullitem
[prefixlen
] = 0x00;
2363 lstrcatW(fullitem
, fd
.cFileName
);
2367 /* Save away any existing for variable context (e.g. nested for loops)
2368 and restore it after executing the body of this for loop */
2370 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2371 forloopcontext
.variable
[varidx
] = fullitem
;
2373 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2374 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2376 cmdEnd
= thisCmdStart
;
2378 } while (FindNextFileW(hff
, &fd
) != 0);
2384 /* Save away any existing for variable context (e.g. nested for loops)
2385 and restore it after executing the body of this for loop */
2387 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2388 forloopcontext
.variable
[varidx
] = fullitem
;
2390 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2391 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2393 cmdEnd
= thisCmdStart
;
2396 } else if (useNumbers
) {
2397 /* Convert the first 3 numbers to signed longs and save */
2398 if (itemNum
<=3) numbers
[itemNum
-1] = wcstol(item
, NULL
, 10);
2399 /* else ignore them! */
2401 /* Filesets - either a list of files, or a command to run and parse the output */
2402 } else if (doFileset
&& ((!forf_usebackq
&& *itemStart
!= '"') ||
2403 (forf_usebackq
&& *itemStart
!= '\''))) {
2408 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum
,
2409 wine_dbgstr_w(item
));
2411 /* If backquote or single quote, we need to launch that command
2412 and parse the results - use a temporary file */
2413 if ((forf_usebackq
&& *itemStart
== '`') ||
2414 (!forf_usebackq
&& *itemStart
== '\'')) {
2416 /* Use itemstart because the command is the whole set, not just the first token */
2417 itemparm
= itemStart
;
2420 /* Use item because the file to process is just the first item in the set */
2423 input
= WCMD_forf_getinputhandle(forf_usebackq
, itemparm
, (itemparm
==itemStart
));
2425 /* Process the input file */
2426 if (input
== INVALID_HANDLE_VALUE
) {
2427 WCMD_print_error ();
2428 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), item
);
2430 return; /* FOR loop aborts at first failure here */
2434 /* Read line by line until end of file */
2435 while (WCMD_fgets(buffer
, ARRAY_SIZE(buffer
), input
)) {
2436 WCMD_parse_line(cmdStart
, firstCmd
, &cmdEnd
, variable
[1], buffer
, &doExecuted
,
2437 &forf_skip
, forf_eol
, forf_delims
, forf_tokens
);
2440 CloseHandle (input
);
2443 /* When we have processed the item as a whole command, abort future set processing */
2444 if (itemparm
==itemStart
) {
2449 /* Filesets - A string literal */
2450 } else if (doFileset
&& ((!forf_usebackq
&& *itemStart
== '"') ||
2451 (forf_usebackq
&& *itemStart
== '\''))) {
2453 /* Remove leading and trailing character, ready to parse with delims= delimiters
2454 Note that the last quote is removed from the set and the string terminates
2455 there to mimic windows */
2456 WCHAR
*strend
= wcsrchr(itemStart
, forf_usebackq
?'\'':'"');
2462 /* Copy the item away from the global buffer used by WCMD_parameter */
2463 lstrcpyW(buffer
, itemStart
);
2464 WCMD_parse_line(cmdStart
, firstCmd
, &cmdEnd
, variable
[1], buffer
, &doExecuted
,
2465 &forf_skip
, forf_eol
, forf_delims
, forf_tokens
);
2467 /* Only one string can be supplied in the whole set, abort future set processing */
2472 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd
);
2476 /* Move onto the next set line */
2477 if (thisSet
) thisSet
= thisSet
->nextcommand
;
2480 /* If /L is provided, now run the for loop */
2483 static const WCHAR fmt
[] = {'%','d','\0'};
2485 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2486 numbers
[0], numbers
[2], numbers
[1]);
2488 (numbers
[1]<0)? i
>=numbers
[2] : i
<=numbers
[2];
2491 swprintf(thisNum
, ARRAY_SIZE(thisNum
), fmt
, i
);
2492 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum
));
2494 thisCmdStart
= cmdStart
;
2497 /* Save away any existing for variable context (e.g. nested for loops)
2498 and restore it after executing the body of this for loop */
2500 oldvariablevalue
= forloopcontext
.variable
[varidx
];
2501 forloopcontext
.variable
[varidx
] = thisNum
;
2503 WCMD_part_execute (&thisCmdStart
, firstCmd
, FALSE
, TRUE
);
2504 if (varidx
>= 0) forloopcontext
.variable
[varidx
] = oldvariablevalue
;
2506 cmdEnd
= thisCmdStart
;
2509 /* If we are walking directories, move on to any which remain */
2510 if (dirsToWalk
!= NULL
) {
2511 DIRECTORY_STACK
*nextDir
= dirsToWalk
->next
;
2512 heap_free(dirsToWalk
->dirName
);
2513 heap_free(dirsToWalk
);
2514 dirsToWalk
= nextDir
;
2515 if (dirsToWalk
) WINE_TRACE("Moving to next directory to iterate: %s\n",
2516 wine_dbgstr_w(dirsToWalk
->dirName
));
2517 else WINE_TRACE("Finished all directories.\n");
2520 } while (dirsToWalk
!= NULL
);
2522 /* Now skip over the do part if we did not perform the for loop so far.
2523 We store in cmdEnd the next command after the do block, but we only
2524 know this if something was run. If it has not been, we need to calculate
2527 thisCmdStart
= cmdStart
;
2528 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2529 WCMD_part_execute(&thisCmdStart
, firstCmd
, FALSE
, FALSE
);
2530 cmdEnd
= thisCmdStart
;
2533 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2534 all processing, OR it should be pointing to the end of && processing OR
2535 it should be pointing at the NULL end of bracket for the DO. The return
2536 value needs to be the NEXT command to execute, which it either is, or
2537 we need to step over the closing bracket */
2539 if (cmdEnd
&& cmdEnd
->command
== NULL
) *cmdList
= cmdEnd
->nextcommand
;
2542 /**************************************************************************
2545 * Simple on-line help. Help text is stored in the resource file.
2548 void WCMD_give_help (const WCHAR
*args
)
2552 args
= WCMD_skip_leading_spaces((WCHAR
*) args
);
2554 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP
));
2557 /* Display help message for builtin commands */
2558 for (i
=0; i
<=WCMD_EXIT
; i
++) {
2559 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2560 args
, -1, inbuilt
[i
], -1) == CSTR_EQUAL
) {
2561 WCMD_output_asis (WCMD_LoadMessage(i
));
2565 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2566 for (i
= 0; i
<= ARRAY_SIZE(externals
); i
++) {
2567 if (CompareStringW(LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
2568 args
, -1, externals
[i
], -1) == CSTR_EQUAL
) {
2570 static const WCHAR helpW
[] = {' ', '/','?','\0'};
2571 lstrcpyW(cmd
, args
);
2572 lstrcatW(cmd
, helpW
);
2573 WCMD_run_program(cmd
, FALSE
);
2577 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP
), args
);
2582 /****************************************************************************
2585 * Batch file jump instruction. Not the most efficient algorithm ;-)
2586 * Prints error message if the specified label cannot be found - the file pointer is
2587 * then at EOF, effectively stopping the batch file.
2588 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2591 void WCMD_goto (CMD_LIST
**cmdList
) {
2593 WCHAR string
[MAX_PATH
];
2594 WCHAR
*labelend
= NULL
;
2595 const WCHAR labelEndsW
[] = {'>','<','|','&',' ',':','\t','\0'};
2597 /* Do not process any more parts of a processed multipart or multilines command */
2598 if (cmdList
) *cmdList
= NULL
;
2600 if (context
!= NULL
) {
2601 WCHAR
*paramStart
= param1
, *str
;
2602 static const WCHAR eofW
[] = {':','e','o','f','\0'};
2604 if (param1
[0] == 0x00) {
2605 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2609 /* Handle special :EOF label */
2610 if (lstrcmpiW (eofW
, param1
) == 0) {
2611 context
-> skip_rest
= TRUE
;
2615 /* Support goto :label as well as goto label plus remove trailing chars */
2616 if (*paramStart
== ':') paramStart
++;
2617 labelend
= wcspbrk(paramStart
, labelEndsW
);
2618 if (labelend
) *labelend
= 0x00;
2619 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart
));
2621 /* Loop through potentially twice - once from current file position
2622 through to the end, and second time from start to current file
2626 LARGE_INTEGER startli
;
2627 for (loop
=0; loop
<2; loop
++) {
2629 /* On first loop, save the file size */
2630 startli
.QuadPart
= 0;
2631 startli
.u
.LowPart
= SetFilePointer(context
-> h
, startli
.u
.LowPart
,
2632 &startli
.u
.HighPart
, FILE_CURRENT
);
2634 /* On second loop, start at the beginning of the file */
2635 WINE_TRACE("Label not found, trying from beginning of file\n");
2636 if (loop
==1) SetFilePointer (context
-> h
, 0, NULL
, FILE_BEGIN
);
2639 while (WCMD_fgets (string
, ARRAY_SIZE(string
), context
-> h
)) {
2642 /* Ignore leading whitespace or no-echo character */
2643 while (*str
=='@' || iswspace (*str
)) str
++;
2645 /* If the first real character is a : then this is a label */
2649 /* Skip spaces between : and label */
2650 while (iswspace (*str
)) str
++;
2651 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str
));
2653 /* Label ends at whitespace or redirection characters */
2654 labelend
= wcspbrk(str
, labelEndsW
);
2655 if (labelend
) *labelend
= 0x00;
2656 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str
));
2658 if (lstrcmpiW (str
, paramStart
) == 0) return;
2661 /* See if we have gone beyond the end point if second time through */
2663 LARGE_INTEGER curli
;
2665 curli
.u
.LowPart
= SetFilePointer(context
-> h
, curli
.u
.LowPart
,
2666 &curli
.u
.HighPart
, FILE_CURRENT
);
2667 if (curli
.QuadPart
> startli
.QuadPart
) {
2668 WINE_TRACE("Reached wrap point, label not found\n");
2676 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET
));
2677 context
-> skip_rest
= TRUE
;
2682 /*****************************************************************************
2685 * Push a directory onto the stack
2688 void WCMD_pushd (const WCHAR
*args
)
2690 struct env_stack
*curdir
;
2692 static const WCHAR parmD
[] = {'/','D','\0'};
2694 if (wcschr(args
, '/') != NULL
) {
2695 SetLastError(ERROR_INVALID_PARAMETER
);
2700 curdir
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
2701 thisdir
= LocalAlloc (LMEM_FIXED
, 1024 * sizeof(WCHAR
));
2702 if( !curdir
|| !thisdir
) {
2705 WINE_ERR ("out of memory\n");
2709 /* Change directory using CD code with /D parameter */
2710 lstrcpyW(quals
, parmD
);
2711 GetCurrentDirectoryW (1024, thisdir
);
2713 WCMD_setshow_default(args
);
2719 curdir
-> next
= pushd_directories
;
2720 curdir
-> strings
= thisdir
;
2721 if (pushd_directories
== NULL
) {
2722 curdir
-> u
.stackdepth
= 1;
2724 curdir
-> u
.stackdepth
= pushd_directories
-> u
.stackdepth
+ 1;
2726 pushd_directories
= curdir
;
2731 /*****************************************************************************
2734 * Pop a directory from the stack
2737 void WCMD_popd (void) {
2738 struct env_stack
*temp
= pushd_directories
;
2740 if (!pushd_directories
)
2743 /* pop the old environment from the stack, and make it the current dir */
2744 pushd_directories
= temp
->next
;
2745 SetCurrentDirectoryW(temp
->strings
);
2746 LocalFree (temp
->strings
);
2750 /*******************************************************************
2751 * evaluate_if_comparison
2753 * Evaluates an "if" comparison operation
2756 * leftOperand [I] left operand, non NULL
2757 * operator [I] "if" binary comparison operator, non NULL
2758 * rightOperand [I] right operand, non NULL
2759 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2762 * Success: 1 if operator applied to the operands evaluates to TRUE
2763 * 0 if operator applied to the operands evaluates to FALSE
2764 * Failure: -1 if operator is not recognized
2766 static int evaluate_if_comparison(const WCHAR
*leftOperand
, const WCHAR
*operator,
2767 const WCHAR
*rightOperand
, int caseInsensitive
)
2769 WCHAR
*endptr_leftOp
, *endptr_rightOp
;
2770 long int leftOperand_int
, rightOperand_int
;
2772 static const WCHAR lssW
[] = {'l','s','s','\0'};
2773 static const WCHAR leqW
[] = {'l','e','q','\0'};
2774 static const WCHAR equW
[] = {'e','q','u','\0'};
2775 static const WCHAR neqW
[] = {'n','e','q','\0'};
2776 static const WCHAR geqW
[] = {'g','e','q','\0'};
2777 static const WCHAR gtrW
[] = {'g','t','r','\0'};
2779 /* == is a special case, as it always compares strings */
2780 if (!lstrcmpiW(operator, eqeqW
))
2781 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) == 0
2782 : lstrcmpW (leftOperand
, rightOperand
) == 0;
2784 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2785 leftOperand_int
= wcstol(leftOperand
, &endptr_leftOp
, 0);
2786 rightOperand_int
= wcstol(rightOperand
, &endptr_rightOp
, 0);
2787 int_operands
= (!*endptr_leftOp
) && (!*endptr_rightOp
);
2789 /* Perform actual (integer or string) comparison */
2790 if (!lstrcmpiW(operator, lssW
)) {
2792 return leftOperand_int
< rightOperand_int
;
2794 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) < 0
2795 : lstrcmpW (leftOperand
, rightOperand
) < 0;
2798 if (!lstrcmpiW(operator, leqW
)) {
2800 return leftOperand_int
<= rightOperand_int
;
2802 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) <= 0
2803 : lstrcmpW (leftOperand
, rightOperand
) <= 0;
2806 if (!lstrcmpiW(operator, equW
)) {
2808 return leftOperand_int
== rightOperand_int
;
2810 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) == 0
2811 : lstrcmpW (leftOperand
, rightOperand
) == 0;
2814 if (!lstrcmpiW(operator, neqW
)) {
2816 return leftOperand_int
!= rightOperand_int
;
2818 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) != 0
2819 : lstrcmpW (leftOperand
, rightOperand
) != 0;
2822 if (!lstrcmpiW(operator, geqW
)) {
2824 return leftOperand_int
>= rightOperand_int
;
2826 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) >= 0
2827 : lstrcmpW (leftOperand
, rightOperand
) >= 0;
2830 if (!lstrcmpiW(operator, gtrW
)) {
2832 return leftOperand_int
> rightOperand_int
;
2834 return caseInsensitive
? lstrcmpiW(leftOperand
, rightOperand
) > 0
2835 : lstrcmpW (leftOperand
, rightOperand
) > 0;
2841 int evaluate_if_condition(WCHAR
*p
, WCHAR
**command
, int *test
, int *negate
)
2843 WCHAR condition
[MAX_PATH
];
2844 static const WCHAR notW
[] = {'n','o','t','\0'};
2845 static const WCHAR errlvlW
[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2846 static const WCHAR existW
[] = {'e','x','i','s','t','\0'};
2847 static const WCHAR defdW
[] = {'d','e','f','i','n','e','d','\0'};
2848 static const WCHAR parmI
[] = {'/','I','\0'};
2849 int caseInsensitive
= (wcsstr(quals
, parmI
) != NULL
);
2851 *negate
= !lstrcmpiW(param1
,notW
);
2852 lstrcpyW(condition
, (*negate
? param2
: param1
));
2853 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition
));
2855 if (!lstrcmpiW (condition
, errlvlW
)) {
2856 WCHAR
*param
= WCMD_parameter(p
, 1+(*negate
), NULL
, FALSE
, FALSE
);
2858 long int param_int
= wcstol(param
, &endptr
, 10);
2859 if (*endptr
) goto syntax_err
;
2860 *test
= ((long int)errorlevel
>= param_int
);
2861 WCMD_parameter(p
, 2+(*negate
), command
, FALSE
, FALSE
);
2863 else if (!lstrcmpiW (condition
, existW
)) {
2864 WIN32_FIND_DATAW fd
;
2866 WCHAR
*param
= WCMD_parameter(p
, 1+(*negate
), NULL
, FALSE
, FALSE
);
2867 int len
= lstrlenW(param
);
2869 /* FindFirstFile does not like a directory path ending in '\', append a '.' */
2870 if (len
&& param
[len
-1] == '\\') lstrcatW(param
, dotW
);
2872 hff
= FindFirstFileW(param
, &fd
);
2873 *test
= (hff
!= INVALID_HANDLE_VALUE
);
2874 if (*test
) FindClose(hff
);
2876 WCMD_parameter(p
, 2+(*negate
), command
, FALSE
, FALSE
);
2878 else if (!lstrcmpiW (condition
, defdW
)) {
2879 *test
= (GetEnvironmentVariableW(WCMD_parameter(p
, 1+(*negate
), NULL
, FALSE
, FALSE
),
2881 WCMD_parameter(p
, 2+(*negate
), command
, FALSE
, FALSE
);
2883 else { /* comparison operation */
2884 WCHAR leftOperand
[MAXSTRING
], rightOperand
[MAXSTRING
], operator[MAXSTRING
];
2887 lstrcpyW(leftOperand
, WCMD_parameter(p
, (*negate
)+caseInsensitive
, ¶mStart
, TRUE
, FALSE
));
2891 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2892 p
= paramStart
+ lstrlenW(leftOperand
);
2893 while (*p
== ' ' || *p
== '\t')
2896 if (!wcsncmp(p
, eqeqW
, lstrlenW(eqeqW
)))
2897 lstrcpyW(operator, eqeqW
);
2899 lstrcpyW(operator, WCMD_parameter(p
, 0, ¶mStart
, FALSE
, FALSE
));
2900 if (!*operator) goto syntax_err
;
2902 p
+= lstrlenW(operator);
2904 lstrcpyW(rightOperand
, WCMD_parameter(p
, 0, ¶mStart
, TRUE
, FALSE
));
2908 *test
= evaluate_if_comparison(leftOperand
, operator, rightOperand
, caseInsensitive
);
2912 p
= paramStart
+ lstrlenW(rightOperand
);
2913 WCMD_parameter(p
, 0, command
, FALSE
, FALSE
);
2922 /****************************************************************************
2925 * Batch file conditional.
2927 * On entry, cmdlist will point to command containing the IF, and optionally
2928 * the first command to execute (if brackets not found)
2929 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2930 * If ('s were found, execute all within that bracket
2931 * Command may optionally be followed by an ELSE - need to skip instructions
2932 * in the else using the same logic
2934 * FIXME: Much more syntax checking needed!
2936 void WCMD_if (WCHAR
*p
, CMD_LIST
**cmdList
)
2938 int negate
; /* Negate condition */
2939 int test
; /* Condition evaluation result */
2942 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
2943 set in a call to WCMD_parse before */
2944 if (evaluate_if_condition(p
, &command
, &test
, &negate
) == -1)
2947 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n",
2948 wine_dbgstr_w(p
), wine_dbgstr_w(quals
), wine_dbgstr_w(param1
),
2949 wine_dbgstr_w(param2
), wine_dbgstr_w(command
));
2951 /* Process rest of IF statement which is on the same line
2952 Note: This may process all or some of the cmdList (eg a GOTO) */
2953 WCMD_part_execute(cmdList
, command
, TRUE
, (test
!= negate
));
2957 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
2960 /****************************************************************************
2963 * Move a file, directory tree or wildcarded set of files.
2966 void WCMD_move (void)
2969 WIN32_FIND_DATAW fd
;
2971 WCHAR input
[MAX_PATH
];
2972 WCHAR output
[MAX_PATH
];
2974 WCHAR dir
[MAX_PATH
];
2975 WCHAR fname
[MAX_PATH
];
2976 WCHAR ext
[MAX_PATH
];
2978 if (param1
[0] == 0x00) {
2979 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
2983 /* If no destination supplied, assume current directory */
2984 if (param2
[0] == 0x00) {
2985 lstrcpyW(param2
, dotW
);
2988 /* If 2nd parm is directory, then use original filename */
2989 /* Convert partial path to full path */
2990 GetFullPathNameW(param1
, ARRAY_SIZE(input
), input
, NULL
);
2991 GetFullPathNameW(param2
, ARRAY_SIZE(output
), output
, NULL
);
2992 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
2993 wine_dbgstr_w(param1
), wine_dbgstr_w(output
));
2995 /* Split into components */
2996 _wsplitpath(input
, drive
, dir
, fname
, ext
);
2998 hff
= FindFirstFileW(input
, &fd
);
2999 if (hff
== INVALID_HANDLE_VALUE
)
3003 WCHAR dest
[MAX_PATH
];
3004 WCHAR src
[MAX_PATH
];
3009 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
3011 /* Build src & dest name */
3012 lstrcpyW(src
, drive
);
3015 /* See if dest is an existing directory */
3016 attribs
= GetFileAttributesW(output
);
3017 if (attribs
!= INVALID_FILE_ATTRIBUTES
&&
3018 (attribs
& FILE_ATTRIBUTE_DIRECTORY
)) {
3019 lstrcpyW(dest
, output
);
3020 lstrcatW(dest
, slashW
);
3021 lstrcatW(dest
, fd
.cFileName
);
3023 lstrcpyW(dest
, output
);
3026 lstrcatW(src
, fd
.cFileName
);
3028 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
3029 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
3031 /* If destination exists, prompt unless /Y supplied */
3032 if (GetFileAttributesW(dest
) != INVALID_FILE_ATTRIBUTES
) {
3034 WCHAR copycmd
[MAXSTRING
];
3037 /* Default whether automatic overwriting is on. If we are interactive then
3038 we prompt by default, otherwise we overwrite by default
3039 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
3040 if (wcsstr (quals
, parmNoY
))
3042 else if (wcsstr (quals
, parmY
))
3045 static const WCHAR copyCmdW
[] = {'C','O','P','Y','C','M','D','\0'};
3046 /* By default, we will force the overwrite in batch mode and ask for
3047 * confirmation in interactive mode. */
3048 force
= !interactive
;
3049 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
3050 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
3051 * default behavior. */
3052 len
= GetEnvironmentVariableW(copyCmdW
, copycmd
, ARRAY_SIZE(copycmd
));
3053 if (len
&& len
< ARRAY_SIZE(copycmd
)) {
3054 if (!lstrcmpiW (copycmd
, parmY
))
3056 else if (!lstrcmpiW (copycmd
, parmNoY
))
3061 /* Prompt if overwriting */
3065 /* Ask for confirmation */
3066 question
= WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE
), dest
);
3067 ok
= WCMD_ask_confirm(question
, FALSE
, NULL
);
3068 LocalFree(question
);
3072 flags
|= MOVEFILE_REPLACE_EXISTING
;
3076 status
= MoveFileExW(src
, dest
, flags
);
3082 WCMD_print_error ();
3085 } while (FindNextFileW(hff
, &fd
) != 0);
3090 /****************************************************************************
3093 * Suspend execution of a batch script until a key is typed
3096 void WCMD_pause (void)
3102 HANDLE hIn
= GetStdHandle(STD_INPUT_HANDLE
);
3104 have_console
= GetConsoleMode(hIn
, &oldmode
);
3106 SetConsoleMode(hIn
, 0);
3108 WCMD_output_asis(anykey
);
3109 WCMD_ReadFile(hIn
, &key
, 1, &count
);
3111 SetConsoleMode(hIn
, oldmode
);
3114 /****************************************************************************
3117 * Delete a directory.
3120 void WCMD_remove_dir (WCHAR
*args
) {
3123 int argsProcessed
= 0;
3125 static const WCHAR parmS
[] = {'/','S','\0'};
3126 static const WCHAR parmQ
[] = {'/','Q','\0'};
3128 /* Loop through all args */
3130 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
3131 if (argN
&& argN
[0] != '/') {
3132 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg
),
3133 wine_dbgstr_w(quals
));
3136 /* If subdirectory search not supplied, just try to remove
3137 and report error if it fails (eg if it contains a file) */
3138 if (wcsstr (quals
, parmS
) == NULL
) {
3139 if (!RemoveDirectoryW(thisArg
)) WCMD_print_error ();
3141 /* Otherwise use ShFileOp to recursively remove a directory */
3144 SHFILEOPSTRUCTW lpDir
;
3147 if (wcsstr (quals
, parmQ
) == NULL
) {
3149 WCHAR question
[MAXSTRING
];
3150 static const WCHAR fmt
[] = {'%','s',' ','\0'};
3152 /* Ask for confirmation */
3153 wsprintfW(question
, fmt
, thisArg
);
3154 ok
= WCMD_ask_confirm(question
, TRUE
, NULL
);
3156 /* Abort if answer is 'N' */
3163 lpDir
.pFrom
= thisArg
;
3164 lpDir
.fFlags
= FOF_SILENT
| FOF_NOCONFIRMATION
| FOF_NOERRORUI
;
3165 lpDir
.wFunc
= FO_DELETE
;
3167 /* SHFileOperationW needs file list with a double null termination */
3168 thisArg
[lstrlenW(thisArg
) + 1] = 0x00;
3170 if (SHFileOperationW(&lpDir
)) WCMD_print_error ();
3175 /* Handle no valid args */
3176 if (argsProcessed
== 0) {
3177 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
3183 /****************************************************************************
3189 void WCMD_rename (void)
3193 WIN32_FIND_DATAW fd
;
3194 WCHAR input
[MAX_PATH
];
3195 WCHAR
*dotDst
= NULL
;
3197 WCHAR dir
[MAX_PATH
];
3198 WCHAR fname
[MAX_PATH
];
3199 WCHAR ext
[MAX_PATH
];
3203 /* Must be at least two args */
3204 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
3205 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
3210 /* Destination cannot contain a drive letter or directory separator */
3211 if ((wcschr(param2
,':') != NULL
) || (wcschr(param2
,'\\') != NULL
)) {
3212 SetLastError(ERROR_INVALID_PARAMETER
);
3218 /* Convert partial path to full path */
3219 GetFullPathNameW(param1
, ARRAY_SIZE(input
), input
, NULL
);
3220 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input
),
3221 wine_dbgstr_w(param1
), wine_dbgstr_w(param2
));
3222 dotDst
= wcschr(param2
, '.');
3224 /* Split into components */
3225 _wsplitpath(input
, drive
, dir
, fname
, ext
);
3227 hff
= FindFirstFileW(input
, &fd
);
3228 if (hff
== INVALID_HANDLE_VALUE
)
3232 WCHAR dest
[MAX_PATH
];
3233 WCHAR src
[MAX_PATH
];
3234 WCHAR
*dotSrc
= NULL
;
3237 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd
.cFileName
));
3239 /* FIXME: If dest name or extension is *, replace with filename/ext
3240 part otherwise use supplied name. This supports:
3242 ren jim.* fred.* etc
3243 However, windows has a more complex algorithm supporting eg
3244 ?'s and *'s mid name */
3245 dotSrc
= wcschr(fd
.cFileName
, '.');
3247 /* Build src & dest name */
3248 lstrcpyW(src
, drive
);
3250 lstrcpyW(dest
, src
);
3251 dirLen
= lstrlenW(src
);
3252 lstrcatW(src
, fd
.cFileName
);
3255 if (param2
[0] == '*') {
3256 lstrcatW(dest
, fd
.cFileName
);
3257 if (dotSrc
) dest
[dirLen
+ (dotSrc
- fd
.cFileName
)] = 0x00;
3259 lstrcatW(dest
, param2
);
3260 if (dotDst
) dest
[dirLen
+ (dotDst
- param2
)] = 0x00;
3263 /* Build Extension */
3264 if (dotDst
&& (*(dotDst
+1)=='*')) {
3265 if (dotSrc
) lstrcatW(dest
, dotSrc
);
3266 } else if (dotDst
) {
3267 lstrcatW(dest
, dotDst
);
3270 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src
));
3271 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest
));
3273 status
= MoveFileW(src
, dest
);
3276 WCMD_print_error ();
3279 } while (FindNextFileW(hff
, &fd
) != 0);
3284 /*****************************************************************************
3287 * Make a copy of the environment.
3289 static WCHAR
*WCMD_dupenv( const WCHAR
*env
)
3299 len
+= (lstrlenW(&env
[len
]) + 1);
3301 env_copy
= LocalAlloc (LMEM_FIXED
, (len
+1) * sizeof (WCHAR
) );
3304 WINE_ERR("out of memory\n");
3307 memcpy (env_copy
, env
, len
*sizeof (WCHAR
));
3313 /*****************************************************************************
3316 * setlocal pushes the environment onto a stack
3317 * Save the environment as unicode so we don't screw anything up.
3319 void WCMD_setlocal (const WCHAR
*s
) {
3321 struct env_stack
*env_copy
;
3322 WCHAR cwd
[MAX_PATH
];
3324 static const WCHAR ondelayW
[] = {'E','N','A','B','L','E','D','E','L','A',
3325 'Y','E','D','E','X','P','A','N','S','I',
3327 static const WCHAR offdelayW
[] = {'D','I','S','A','B','L','E','D','E','L',
3328 'A','Y','E','D','E','X','P','A','N','S',
3331 /* setlocal does nothing outside of batch programs */
3332 if (!context
) return;
3334 /* DISABLEEXTENSIONS ignored */
3336 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3337 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3338 if (!wcsicmp(param1
, ondelayW
) || !wcsicmp(param2
, ondelayW
)) {
3340 } else if (!wcsicmp(param1
, offdelayW
) || !wcsicmp(param2
, offdelayW
)) {
3343 newdelay
= delayedsubst
;
3345 WINE_TRACE("Setting delayed expansion to %d\n", newdelay
);
3347 env_copy
= LocalAlloc (LMEM_FIXED
, sizeof (struct env_stack
));
3350 WINE_ERR ("out of memory\n");
3354 env
= GetEnvironmentStringsW ();
3355 env_copy
->strings
= WCMD_dupenv (env
);
3356 if (env_copy
->strings
)
3358 env_copy
->batchhandle
= context
->h
;
3359 env_copy
->next
= saved_environment
;
3360 env_copy
->delayedsubst
= delayedsubst
;
3361 delayedsubst
= newdelay
;
3362 saved_environment
= env_copy
;
3364 /* Save the current drive letter */
3365 GetCurrentDirectoryW(MAX_PATH
, cwd
);
3366 env_copy
->u
.cwd
= cwd
[0];
3369 LocalFree (env_copy
);
3371 FreeEnvironmentStringsW (env
);
3375 /*****************************************************************************
3378 * endlocal pops the environment off a stack
3379 * Note: When searching for '=', search from WCHAR position 1, to handle
3380 * special internal environment variables =C:, =D: etc
3382 void WCMD_endlocal (void) {
3383 WCHAR
*env
, *old
, *p
;
3384 struct env_stack
*temp
;
3387 /* setlocal does nothing outside of batch programs */
3388 if (!context
) return;
3390 /* setlocal needs a saved environment from within the same context (batch
3391 program) as it was saved in */
3392 if (!saved_environment
|| saved_environment
->batchhandle
!= context
->h
)
3395 /* pop the old environment from the stack */
3396 temp
= saved_environment
;
3397 saved_environment
= temp
->next
;
3399 /* delete the current environment, totally */
3400 env
= GetEnvironmentStringsW ();
3401 old
= WCMD_dupenv (env
);
3404 n
= lstrlenW(&old
[len
]) + 1;
3405 p
= wcschr(&old
[len
] + 1, '=');
3409 SetEnvironmentVariableW (&old
[len
], NULL
);
3414 FreeEnvironmentStringsW (env
);
3416 /* restore old environment */
3417 env
= temp
->strings
;
3419 delayedsubst
= temp
->delayedsubst
;
3420 WINE_TRACE("Delayed expansion now %d\n", delayedsubst
);
3422 n
= lstrlenW(&env
[len
]) + 1;
3423 p
= wcschr(&env
[len
] + 1, '=');
3427 SetEnvironmentVariableW (&env
[len
], p
);
3432 /* Restore current drive letter */
3433 if (IsCharAlphaW(temp
->u
.cwd
)) {
3435 WCHAR cwd
[MAX_PATH
];
3436 static const WCHAR fmt
[] = {'=','%','c',':','\0'};
3438 wsprintfW(envvar
, fmt
, temp
->u
.cwd
);
3439 if (GetEnvironmentVariableW(envvar
, cwd
, MAX_PATH
)) {
3440 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd
));
3441 SetCurrentDirectoryW(cwd
);
3449 /*****************************************************************************
3450 * WCMD_setshow_default
3452 * Set/Show the current default directory
3455 void WCMD_setshow_default (const WCHAR
*args
) {
3461 WIN32_FIND_DATAW fd
;
3463 static const WCHAR parmD
[] = {'/','D','\0'};
3465 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args
));
3467 /* Skip /D and trailing whitespace if on the front of the command line */
3468 if (lstrlenW(args
) >= 2 &&
3469 CompareStringW(LOCALE_USER_DEFAULT
,
3470 NORM_IGNORECASE
| SORT_STRINGSORT
,
3471 args
, 2, parmD
, -1) == CSTR_EQUAL
) {
3473 while (*args
&& (*args
==' ' || *args
=='\t'))
3477 GetCurrentDirectoryW(ARRAY_SIZE(cwd
), cwd
);
3480 lstrcatW (cwd
, newlineW
);
3481 WCMD_output_asis (cwd
);
3484 /* Remove any double quotes, which may be in the
3485 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3488 if (*args
!= '"') *pos
++ = *args
;
3491 while (pos
> string
&& (*(pos
-1) == ' ' || *(pos
-1) == '\t'))
3495 /* Search for appropriate directory */
3496 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string
));
3497 hff
= FindFirstFileW(string
, &fd
);
3498 if (hff
!= INVALID_HANDLE_VALUE
) {
3500 if (fd
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
) {
3501 WCHAR fpath
[MAX_PATH
];
3503 WCHAR dir
[MAX_PATH
];
3504 WCHAR fname
[MAX_PATH
];
3505 WCHAR ext
[MAX_PATH
];
3506 static const WCHAR fmt
[] = {'%','s','%','s','%','s','\0'};
3508 /* Convert path into actual directory spec */
3509 GetFullPathNameW(string
, ARRAY_SIZE(fpath
), fpath
, NULL
);
3510 _wsplitpath(fpath
, drive
, dir
, fname
, ext
);
3513 wsprintfW(string
, fmt
, drive
, dir
, fd
.cFileName
);
3516 } while (FindNextFileW(hff
, &fd
) != 0);
3520 /* Change to that directory */
3521 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string
));
3523 status
= SetCurrentDirectoryW(string
);
3526 WCMD_print_error ();
3530 /* Save away the actual new directory, to store as current location */
3531 GetCurrentDirectoryW(ARRAY_SIZE(string
), string
);
3533 /* Restore old directory if drive letter would change, and
3534 CD x:\directory /D (or pushd c:\directory) not supplied */
3535 if ((wcsstr(quals
, parmD
) == NULL
) &&
3536 (param1
[1] == ':') && (toupper(param1
[0]) != toupper(cwd
[0]))) {
3537 SetCurrentDirectoryW(cwd
);
3541 /* Set special =C: type environment variable, for drive letter of
3542 change of directory, even if path was restored due to missing
3543 /D (allows changing drive letter when not resident on that
3545 if ((string
[1] == ':') && IsCharAlphaW(string
[0])) {
3547 lstrcpyW(env
, equalW
);
3548 memcpy(env
+1, string
, 2 * sizeof(WCHAR
));
3550 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env
), wine_dbgstr_w(string
));
3551 SetEnvironmentVariableW(env
, string
);
3558 /****************************************************************************
3561 * Set/Show the system date
3562 * FIXME: Can't change date yet
3565 void WCMD_setshow_date (void) {
3567 WCHAR curdate
[64], buffer
[64];
3569 static const WCHAR parmT
[] = {'/','T','\0'};
3572 if (GetDateFormatW(LOCALE_USER_DEFAULT
, 0, NULL
, NULL
, curdate
, ARRAY_SIZE(curdate
))) {
3573 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE
), curdate
);
3574 if (wcsstr (quals
, parmT
) == NULL
) {
3575 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE
));
3576 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, ARRAY_SIZE(buffer
), &count
);
3578 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
3582 else WCMD_print_error ();
3585 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
3589 /****************************************************************************
3591 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3594 static int __cdecl
WCMD_compare( const void *a
, const void *b
)
3597 const WCHAR
* const *str_a
= a
, * const *str_b
= b
;
3598 r
= CompareStringW( LOCALE_USER_DEFAULT
, NORM_IGNORECASE
| SORT_STRINGSORT
,
3599 *str_a
, wcscspn(*str_a
, equalW
), *str_b
, wcscspn(*str_b
, equalW
) );
3600 if( r
== CSTR_LESS_THAN
) return -1;
3601 if( r
== CSTR_GREATER_THAN
) return 1;
3605 /****************************************************************************
3606 * WCMD_setshow_sortenv
3608 * sort variables into order for display
3609 * Optionally only display those who start with a stub
3610 * returns the count displayed
3612 static int WCMD_setshow_sortenv(const WCHAR
*s
, const WCHAR
*stub
)
3614 UINT count
=0, len
=0, i
, displayedcount
=0, stublen
=0;
3617 if (stub
) stublen
= lstrlenW(stub
);
3619 /* count the number of strings, and the total length */
3621 len
+= (lstrlenW(&s
[len
]) + 1);
3625 /* add the strings to an array */
3626 str
= LocalAlloc (LMEM_FIXED
| LMEM_ZEROINIT
, count
* sizeof (WCHAR
*) );
3630 for( i
=1; i
<count
; i
++ )
3631 str
[i
] = str
[i
-1] + lstrlenW(str
[i
-1]) + 1;
3633 /* sort the array */
3634 qsort( str
, count
, sizeof (WCHAR
*), WCMD_compare
);
3637 for( i
=0; i
<count
; i
++ ) {
3638 if (!stub
|| CompareStringW(LOCALE_USER_DEFAULT
,
3639 NORM_IGNORECASE
| SORT_STRINGSORT
,
3640 str
[i
], stublen
, stub
, -1) == CSTR_EQUAL
) {
3641 /* Don't display special internal variables */
3642 if (str
[i
][0] != '=') {
3643 WCMD_output_asis(str
[i
]);
3644 WCMD_output_asis(newlineW
);
3651 return displayedcount
;
3654 /****************************************************************************
3655 * WCMD_getprecedence
3656 * Return the precedence of a particular operator
3658 static int WCMD_getprecedence(const WCHAR in
)
3699 /****************************************************************************
3701 * Push either a number or name (environment variable) onto the supplied
3704 static void WCMD_pushnumber(WCHAR
*var
, int num
, VARSTACK
**varstack
) {
3705 VARSTACK
*thisstack
= heap_xalloc(sizeof(VARSTACK
));
3706 thisstack
->isnum
= (var
== NULL
);
3708 thisstack
->variable
= var
;
3709 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var
));
3711 thisstack
->value
= num
;
3712 WINE_TRACE("Pushed number %d\n", num
);
3714 thisstack
->next
= *varstack
;
3715 *varstack
= thisstack
;
3718 /****************************************************************************
3720 * Returns the value of the top number or environment variable on the stack
3721 * and leaves the item on the stack.
3723 static int WCMD_peeknumber(VARSTACK
**varstack
) {
3728 thisvar
= *varstack
;
3729 if (!thisvar
->isnum
) {
3730 WCHAR tmpstr
[MAXSTRING
];
3731 if (GetEnvironmentVariableW(thisvar
->variable
, tmpstr
, MAXSTRING
)) {
3732 result
= wcstol(tmpstr
,NULL
,0);
3734 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar
->variable
), result
);
3736 result
= thisvar
->value
;
3739 WINE_TRACE("Peeked number %d\n", result
);
3743 /****************************************************************************
3745 * Returns the value of the top number or environment variable on the stack
3746 * and removes the item from the stack.
3748 static int WCMD_popnumber(VARSTACK
**varstack
) {
3753 thisvar
= *varstack
;
3754 result
= WCMD_peeknumber(varstack
);
3755 if (!thisvar
->isnum
) heap_free(thisvar
->variable
);
3756 *varstack
= thisvar
->next
;
3759 WINE_TRACE("Popped number %d\n", result
);
3763 /****************************************************************************
3765 * Push an operator onto the supplied stack
3767 static void WCMD_pushoperator(WCHAR op
, int precedence
, OPSTACK
**opstack
) {
3768 OPSTACK
*thisstack
= heap_xalloc(sizeof(OPSTACK
));
3769 thisstack
->precedence
= precedence
;
3771 thisstack
->next
= *opstack
;
3772 WINE_TRACE("Pushed operator %c\n", op
);
3773 *opstack
= thisstack
;
3776 /****************************************************************************
3778 * Returns the operator from the top of the stack and removes the item from
3781 static WCHAR
WCMD_popoperator(OPSTACK
**opstack
) {
3787 result
= thisop
->op
;
3788 *opstack
= thisop
->next
;
3791 WINE_TRACE("Popped operator %c\n", result
);
3795 /****************************************************************************
3797 * Actions the top operator on the stack against the first and sometimes
3798 * second value on the variable stack, and pushes the result
3799 * Returns non-zero on error.
3801 static int WCMD_reduce(OPSTACK
**opstack
, VARSTACK
**varstack
) {
3806 if (!*opstack
|| !*varstack
) {
3807 WINE_TRACE("No operators for the reduce\n");
3808 return WCMD_NOOPERATOR
;
3811 /* Remove the top operator */
3812 thisop
= WCMD_popoperator(opstack
);
3813 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop
);
3815 /* One variable operators */
3816 var1
= WCMD_popnumber(varstack
);
3818 case '!': WCMD_pushnumber(NULL
, !var1
, varstack
);
3820 case '~': WCMD_pushnumber(NULL
, ~var1
, varstack
);
3822 case OP_POSITIVE
: WCMD_pushnumber(NULL
, var1
, varstack
);
3824 case OP_NEGATIVE
: WCMD_pushnumber(NULL
, -var1
, varstack
);
3828 /* Two variable operators */
3830 WINE_TRACE("No operands left for the reduce?\n");
3831 return WCMD_NOOPERAND
;
3838 break; /* Handled above */
3839 case '*': var2
= WCMD_popnumber(varstack
);
3840 WCMD_pushnumber(NULL
, var2
*var1
, varstack
);
3842 case '/': var2
= WCMD_popnumber(varstack
);
3843 if (var1
== 0) return WCMD_DIVIDEBYZERO
;
3844 WCMD_pushnumber(NULL
, var2
/var1
, varstack
);
3846 case '+': var2
= WCMD_popnumber(varstack
);
3847 WCMD_pushnumber(NULL
, var2
+var1
, varstack
);
3849 case '-': var2
= WCMD_popnumber(varstack
);
3850 WCMD_pushnumber(NULL
, var2
-var1
, varstack
);
3852 case '&': var2
= WCMD_popnumber(varstack
);
3853 WCMD_pushnumber(NULL
, var2
&var1
, varstack
);
3855 case '%': var2
= WCMD_popnumber(varstack
);
3856 if (var1
== 0) return WCMD_DIVIDEBYZERO
;
3857 WCMD_pushnumber(NULL
, var2
%var1
, varstack
);
3859 case '^': var2
= WCMD_popnumber(varstack
);
3860 WCMD_pushnumber(NULL
, var2
^var1
, varstack
);
3862 case '<': var2
= WCMD_popnumber(varstack
);
3863 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3864 which differs from the compiler (for example gcc) so being explicit. */
3865 if (var1
< 0 || var1
>= (8 * sizeof(INT
))) {
3866 WCMD_pushnumber(NULL
, 0, varstack
);
3868 WCMD_pushnumber(NULL
, var2
<<var1
, varstack
);
3871 case '>': var2
= WCMD_popnumber(varstack
);
3872 WCMD_pushnumber(NULL
, var2
>>var1
, varstack
);
3874 case '|': var2
= WCMD_popnumber(varstack
);
3875 WCMD_pushnumber(NULL
, var2
|var1
, varstack
);
3891 /* The left of an equals must be one variable */
3892 if (!(*varstack
) || (*varstack
)->isnum
) {
3893 return WCMD_NOOPERAND
;
3896 /* Make the number stack grow by inserting the value of the variable */
3897 var2
= WCMD_peeknumber(varstack
);
3898 WCMD_pushnumber(NULL
, var2
, varstack
);
3899 WCMD_pushnumber(NULL
, var1
, varstack
);
3901 /* Make the operand stack grow by pushing the assign operator plus the
3902 operator to perform */
3903 while (calcassignments
[i
].op
!= ' ' &&
3904 calcassignments
[i
].calculatedop
!= thisop
) {
3907 if (calcassignments
[i
].calculatedop
== ' ') {
3908 WINE_ERR("Unexpected operator %c\n", thisop
);
3909 return WCMD_NOOPERATOR
;
3911 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack
);
3912 WCMD_pushoperator(calcassignments
[i
].op
,
3913 WCMD_getprecedence(calcassignments
[i
].op
), opstack
);
3919 WCHAR intFormat
[] = {'%','d','\0'};
3920 WCHAR result
[MAXSTRING
];
3922 /* Build the result, then push it onto the stack */
3923 swprintf(result
, ARRAY_SIZE(result
), intFormat
, var1
);
3924 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack
)->variable
),
3925 wine_dbgstr_w(result
));
3926 SetEnvironmentVariableW((*varstack
)->variable
, result
);
3927 var2
= WCMD_popnumber(varstack
);
3928 WCMD_pushnumber(NULL
, var1
, varstack
);
3932 default: WINE_ERR("Unrecognized operator %c\n", thisop
);
3939 /****************************************************************************
3940 * WCMD_handleExpression
3941 * Handles an expression provided to set /a - If it finds brackets, it uses
3942 * recursion to process the parts in brackets.
3944 static int WCMD_handleExpression(WCHAR
**expr
, int *ret
, int depth
)
3946 static const WCHAR mathDelims
[] = {' ','\t','(',')','!','~','-','*','/','%',
3947 '+','<','>','&','^','|','=',',','\0' };
3950 BOOL lastwasnumber
= FALSE
; /* FALSE makes a minus at the start of the expression easier to handle */
3951 OPSTACK
*opstackhead
= NULL
;
3952 VARSTACK
*varstackhead
= NULL
;
3953 WCHAR foundhalf
= 0;
3956 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr
));
3959 /* Iterate through until whole expression is processed */
3960 while (pos
&& *pos
) {
3963 /* Skip whitespace to get to the next character to process*/
3964 while (*pos
&& (*pos
==' ' || *pos
=='\t')) pos
++;
3965 if (!*pos
) goto exprreturn
;
3967 /* If we have found anything other than an operator then it's a number/variable */
3968 if (wcschr(mathDelims
, *pos
) == NULL
) {
3969 WCHAR
*parmstart
, *parm
, *dupparm
;
3972 /* Cannot have an expression with var/number twice, without an operator
3973 in-between, nor or number following a half constructed << or >> operator */
3974 if (lastwasnumber
|| foundhalf
) {
3975 rc
= WCMD_NOOPERATOR
;
3976 goto exprerrorreturn
;
3978 lastwasnumber
= TRUE
;
3980 if (iswdigit(*pos
)) {
3981 /* For a number - just push it onto the stack */
3982 int num
= wcstoul(pos
, &nextpos
, 0);
3983 WCMD_pushnumber(NULL
, num
, &varstackhead
);
3986 /* Verify the number was validly formed */
3987 if (*nextpos
&& (wcschr(mathDelims
, *nextpos
) == NULL
)) {
3988 rc
= WCMD_BADHEXOCT
;
3989 goto exprerrorreturn
;
3993 /* For a variable - just push it onto the stack */
3994 parm
= WCMD_parameter_with_delims(pos
, 0, &parmstart
, FALSE
, FALSE
, mathDelims
);
3995 dupparm
= heap_strdupW(parm
);
3996 WCMD_pushnumber(dupparm
, 0, &varstackhead
);
3997 pos
= parmstart
+ lstrlenW(dupparm
);
4002 /* We have found an operator. Some operators are one character, some two, and the minus
4003 and plus signs need special processing as they can be either operators or just influence
4004 the parameter which follows them */
4005 if (foundhalf
&& (*pos
!= foundhalf
)) {
4006 /* Badly constructed operator pair */
4007 rc
= WCMD_NOOPERATOR
;
4008 goto exprerrorreturn
;
4011 treatasnumber
= FALSE
; /* We are processing an operand */
4014 /* > and < are special as they are double character operators (and spaces can be between them!)
4015 If we see these for the first time, set a flag, and second time around we continue.
4016 Note these double character operators are stored as just one of the characters on the stack */
4018 case '<': if (!foundhalf
) {
4023 /* We have found the rest, so clear up the knowledge of the half completed part and
4024 drop through to normal operator processing */
4028 case '=': if (*pos
=='=') {
4029 /* = is special cased as if the last was an operator then we may have e.g. += or
4030 *= etc which we need to handle by replacing the operator that is on the stack
4031 with a calculated assignment equivalent */
4032 if (!lastwasnumber
&& opstackhead
) {
4034 while (calcassignments
[i
].op
!= ' ' && calcassignments
[i
].op
!= opstackhead
->op
) {
4037 if (calcassignments
[i
].op
== ' ') {
4038 rc
= WCMD_NOOPERAND
;
4039 goto exprerrorreturn
;
4041 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
4042 when the general operator handling happens further down. */
4043 *pos
= calcassignments
[i
].calculatedop
;
4044 WCMD_popoperator(&opstackhead
);
4050 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
4051 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
4052 case '+': if (!lastwasnumber
&& *pos
=='+') *pos
= OP_POSITIVE
;
4054 case '-': if (!lastwasnumber
&& *pos
=='-') *pos
= OP_NEGATIVE
;
4057 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
4058 case '!': /* drop through */
4059 case '~': /* drop through */
4060 case '/': /* drop through */
4061 case '%': /* drop through */
4062 case '&': /* drop through */
4063 case '^': /* drop through */
4064 case '*': /* drop through */
4066 /* General code for handling most of the operators - look at the
4067 precedence of the top item on the stack, and see if we need to
4068 action the stack before we push something else onto it. */
4070 int precedence
= WCMD_getprecedence(*pos
);
4071 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos
,
4072 precedence
, !opstackhead
?-1:opstackhead
->precedence
);
4074 /* In general, for things with the same precedence, reduce immediately
4075 except for assignments and unary operators which do not */
4076 while (!rc
&& opstackhead
&&
4077 ((opstackhead
->precedence
> precedence
) ||
4078 ((opstackhead
->precedence
== precedence
) &&
4079 (precedence
!= 1) && (precedence
!= 8)))) {
4080 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
4082 if (rc
) goto exprerrorreturn
;
4083 WCMD_pushoperator(*pos
, precedence
, &opstackhead
);
4088 /* comma means start a new expression, ie calculate what we have */
4091 int prevresult
= -1;
4092 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
4093 while (!rc
&& opstackhead
) {
4094 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
4096 if (rc
) goto exprerrorreturn
;
4097 /* If we have anything other than one number left, error
4098 otherwise throw the number away */
4099 if (!varstackhead
|| varstackhead
->next
) {
4100 rc
= WCMD_NOOPERATOR
;
4101 goto exprerrorreturn
;
4103 prevresult
= WCMD_popnumber(&varstackhead
);
4104 WINE_TRACE("Expression resolved to %d\n", prevresult
);
4105 heap_free(varstackhead
);
4106 varstackhead
= NULL
;
4111 /* Open bracket - use iteration to parse the inner expression, then continue */
4115 rc
= WCMD_handleExpression(&pos
, &exprresult
, depth
+1);
4116 if (rc
) goto exprerrorreturn
;
4117 WCMD_pushnumber(NULL
, exprresult
, &varstackhead
);
4121 /* Close bracket - we have finished this depth, calculate and return */
4124 treatasnumber
= TRUE
; /* Things in brackets result in a number */
4127 goto exprerrorreturn
;
4133 WINE_ERR("Unrecognized operator %c\n", *pos
);
4136 lastwasnumber
= treatasnumber
;
4142 /* We need to reduce until we have a single number (or variable) on the
4143 stack and set the return value to that */
4144 while (!rc
&& opstackhead
) {
4145 rc
= WCMD_reduce(&opstackhead
, &varstackhead
);
4147 if (rc
) goto exprerrorreturn
;
4149 /* If we have anything other than one number left, error
4150 otherwise throw the number away */
4151 if (!varstackhead
|| varstackhead
->next
) {
4152 rc
= WCMD_NOOPERATOR
;
4153 goto exprerrorreturn
;
4156 /* Now get the number (and convert if it's just a variable name) */
4157 *ret
= WCMD_popnumber(&varstackhead
);
4160 /* Free all remaining memory */
4161 while (opstackhead
) WCMD_popoperator(&opstackhead
);
4162 while (varstackhead
) WCMD_popnumber(&varstackhead
);
4164 WINE_TRACE("Returning result %d, rc %d\n", *ret
, rc
);
4168 /****************************************************************************
4171 * Set/Show the environment variables
4174 void WCMD_setshow_env (WCHAR
*s
) {
4179 static const WCHAR parmP
[] = {'/','P','\0'};
4180 static const WCHAR parmA
[] = {'/','A','\0'};
4181 WCHAR string
[MAXSTRING
];
4183 if (param1
[0] == 0x00 && quals
[0] == 0x00) {
4184 env
= GetEnvironmentStringsW();
4185 WCMD_setshow_sortenv( env
, NULL
);
4189 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4190 if (CompareStringW(LOCALE_USER_DEFAULT
,
4191 NORM_IGNORECASE
| SORT_STRINGSORT
,
4192 s
, 2, parmP
, -1) == CSTR_EQUAL
) {
4196 while (*s
&& (*s
==' ' || *s
=='\t')) s
++;
4197 /* set /P "var=value"jim ignores anything after the last quote */
4200 lastquote
= WCMD_strip_quotes(s
);
4201 if (lastquote
) *lastquote
= 0x00;
4202 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s
));
4205 /* If no parameter, or no '=' sign, return an error */
4206 if (!(*s
) || ((p
= wcschr (s
, '=')) == NULL
)) {
4207 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
4211 /* Output the prompt */
4213 if (*p
) WCMD_output_asis(p
);
4215 /* Read the reply */
4216 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, ARRAY_SIZE(string
), &count
);
4218 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
4219 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
4220 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
4221 wine_dbgstr_w(string
));
4222 SetEnvironmentVariableW(s
, string
);
4225 /* See if /A supplied, and if so calculate the results of all the expressions */
4226 } else if (CompareStringW(LOCALE_USER_DEFAULT
,
4227 NORM_IGNORECASE
| SORT_STRINGSORT
,
4228 s
, 2, parmA
, -1) == CSTR_EQUAL
) {
4229 /* /A supplied, so evaluate expressions and set variables appropriately */
4230 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4231 /* of the final computation */
4237 /* Remove all quotes before doing any calculations */
4238 thisexpr
= heap_xalloc((lstrlenW(s
+2)+1) * sizeof(WCHAR
));
4242 if (*src
!= '"') *dst
++ = *src
;
4247 /* Now calculate the results of the expression */
4249 rc
= WCMD_handleExpression(&src
, &result
, 0);
4250 heap_free(thisexpr
);
4252 /* If parsing failed, issue the error message */
4254 WCMD_output_stderr(WCMD_LoadMessage(rc
));
4258 /* If we have no context (interactive or cmd.exe /c) print the final result */
4260 static const WCHAR fmt
[] = {'%','d','\0'};
4261 swprintf(string
, ARRAY_SIZE(string
), fmt
, result
);
4262 WCMD_output_asis(string
);
4268 /* set "var=value"jim ignores anything after the last quote */
4271 lastquote
= WCMD_strip_quotes(s
);
4272 if (lastquote
) *lastquote
= 0x00;
4273 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s
));
4276 p
= wcschr (s
, '=');
4278 env
= GetEnvironmentStringsW();
4279 if (WCMD_setshow_sortenv( env
, s
) == 0) {
4280 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV
), s
);
4288 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s
),
4290 status
= SetEnvironmentVariableW(s
, p
);
4291 gle
= GetLastError();
4292 if ((!status
) & (gle
== ERROR_ENVVAR_NOT_FOUND
)) {
4294 } else if (!status
) WCMD_print_error();
4295 else errorlevel
= 0;
4299 /****************************************************************************
4302 * Set/Show the path environment variable
4305 void WCMD_setshow_path (const WCHAR
*args
) {
4309 static const WCHAR pathW
[] = {'P','A','T','H','\0'};
4310 static const WCHAR pathEqW
[] = {'P','A','T','H','=','\0'};
4312 if (!*param1
&& !*param2
) {
4313 status
= GetEnvironmentVariableW(pathW
, string
, ARRAY_SIZE(string
));
4315 WCMD_output_asis ( pathEqW
);
4316 WCMD_output_asis ( string
);
4317 WCMD_output_asis ( newlineW
);
4320 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH
));
4324 if (*args
== '=') args
++; /* Skip leading '=' */
4325 status
= SetEnvironmentVariableW(pathW
, args
);
4326 if (!status
) WCMD_print_error();
4330 /****************************************************************************
4331 * WCMD_setshow_prompt
4333 * Set or show the command prompt.
4336 void WCMD_setshow_prompt (void) {
4339 static const WCHAR promptW
[] = {'P','R','O','M','P','T','\0'};
4342 SetEnvironmentVariableW(promptW
, NULL
);
4346 while ((*s
== '=') || (*s
== ' ') || (*s
== '\t')) s
++;
4348 SetEnvironmentVariableW(promptW
, NULL
);
4350 else SetEnvironmentVariableW(promptW
, s
);
4354 /****************************************************************************
4357 * Set/Show the system time
4358 * FIXME: Can't change time yet
4361 void WCMD_setshow_time (void) {
4363 WCHAR curtime
[64], buffer
[64];
4366 static const WCHAR parmT
[] = {'/','T','\0'};
4370 if (GetTimeFormatW(LOCALE_USER_DEFAULT
, 0, &st
, NULL
, curtime
, ARRAY_SIZE(curtime
))) {
4371 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME
), curtime
);
4372 if (wcsstr (quals
, parmT
) == NULL
) {
4373 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME
));
4374 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, ARRAY_SIZE(buffer
), &count
);
4376 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
4380 else WCMD_print_error ();
4383 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI
));
4387 /****************************************************************************
4390 * Shift batch parameters.
4391 * Optional /n says where to start shifting (n=0-8)
4394 void WCMD_shift (const WCHAR
*args
) {
4397 if (context
!= NULL
) {
4398 WCHAR
*pos
= wcschr(args
, '/');
4403 } else if (*(pos
+1)>='0' && *(pos
+1)<='8') {
4404 start
= (*(pos
+1) - '0');
4406 SetLastError(ERROR_INVALID_PARAMETER
);
4411 WINE_TRACE("Shifting variables, starting at %d\n", start
);
4412 for (i
=start
;i
<=8;i
++) {
4413 context
-> shift_count
[i
] = context
-> shift_count
[i
+1] + 1;
4415 context
-> shift_count
[9] = context
-> shift_count
[9] + 1;
4420 /****************************************************************************
4423 void WCMD_start(WCHAR
*args
)
4425 static const WCHAR exeW
[] = {'\\','c','o','m','m','a','n','d',
4426 '\\','s','t','a','r','t','.','e','x','e',0};
4427 static const WCHAR startDelims
[] = { ' ', '\t', '/', '\0' };
4428 static const WCHAR prefixQuote
[] = {'"','\\','"','\0'};
4429 static const WCHAR postfixQuote
[] = {'\\','"','"','\0'};
4432 WCHAR file
[MAX_PATH
];
4433 WCHAR
*cmdline
, *cmdline_params
;
4435 PROCESS_INFORMATION pi
;
4437 GetWindowsDirectoryW( file
, MAX_PATH
);
4438 lstrcatW( file
, exeW
);
4439 cmdline
= heap_xalloc( (lstrlenW(file
) + lstrlenW(args
) + 8) * sizeof(WCHAR
) );
4440 lstrcpyW( cmdline
, file
);
4441 lstrcatW( cmdline
, spaceW
);
4442 cmdline_params
= cmdline
+ lstrlenW(cmdline
);
4444 /* The start built-in has some special command-line parsing properties
4445 * which will be outlined here.
4447 * both '\t' and ' ' are argument separators
4448 * '/' has a special double role as both separator and switch prefix, e.g.
4454 * are valid ways to pass multiple options to start. In the latter case
4455 * '/i' is not a part of the title but parsed as a switch.
4457 * However, '=', ';' and ',' are not separators:
4458 * > start "deus"=ex,machina
4460 * will in fact open a console titled 'deus=ex,machina'
4462 * The title argument parsing code is only interested in quotes themselves,
4463 * it does not respect escaping of any kind and all quotes are dropped
4464 * from the resulting title, therefore:
4466 * > start "\"" hello"/low
4468 * actually opens a console titled '\ hello' with low priorities.
4470 * To not break compatibility with wine programs relying on
4471 * wine's separate 'start.exe', this program's peculiar console
4472 * title parsing is actually implemented in 'cmd.exe' which is the
4473 * application native Windows programs will use to invoke 'start'.
4475 * WCMD_parameter_with_delims will take care of everything for us.
4478 for (argno
=0; ; argno
++) {
4479 WCHAR
*thisArg
, *argN
;
4482 thisArg
= WCMD_parameter_with_delims(args
, argno
, &argN
, FALSE
, FALSE
, startDelims
);
4484 /* No more parameters */
4488 /* Found the title */
4489 if (argN
[0] == '"') {
4490 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg
));
4493 /* Copy all of the cmdline processed */
4494 memcpy(cmdline_params
, args
, sizeof(WCHAR
) * (argN
- args
));
4495 cmdline_params
[argN
- args
] = '\0';
4497 /* Add quoted title */
4498 lstrcatW(cmdline_params
, prefixQuote
);
4499 lstrcatW(cmdline_params
, thisArg
);
4500 lstrcatW(cmdline_params
, postfixQuote
);
4502 /* Concatenate remaining command-line */
4503 thisArg
= WCMD_parameter_with_delims(args
, argno
, &argN
, TRUE
, FALSE
, startDelims
);
4504 lstrcatW(cmdline_params
, argN
+ lstrlenW(thisArg
));
4509 /* Skipping a regular argument? */
4510 else if (argN
!= args
&& argN
[-1] == '/') {
4513 /* Not an argument nor the title, start of program arguments,
4514 * stop looking for title.
4520 /* build command-line if not built yet */
4522 lstrcatW( cmdline
, args
);
4525 memset( &st
, 0, sizeof(STARTUPINFOW
) );
4526 st
.cb
= sizeof(STARTUPINFOW
);
4528 if (CreateProcessW( file
, cmdline
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st
, &pi
))
4530 WaitForSingleObject( pi
.hProcess
, INFINITE
);
4531 GetExitCodeProcess( pi
.hProcess
, &errorlevel
);
4532 if (errorlevel
== STILL_ACTIVE
) errorlevel
= 0;
4533 CloseHandle(pi
.hProcess
);
4534 CloseHandle(pi
.hThread
);
4538 SetLastError(ERROR_FILE_NOT_FOUND
);
4539 WCMD_print_error ();
4545 /****************************************************************************
4548 * Set the console title
4550 void WCMD_title (const WCHAR
*args
) {
4551 SetConsoleTitleW(args
);
4554 /****************************************************************************
4557 * Copy a file to standard output.
4560 void WCMD_type (WCHAR
*args
) {
4564 BOOL writeHeaders
= FALSE
;
4566 if (param1
[0] == 0x00) {
4567 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
4571 if (param2
[0] != 0x00) writeHeaders
= TRUE
;
4573 /* Loop through all args */
4576 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
4584 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
4585 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4586 FILE_ATTRIBUTE_NORMAL
, NULL
);
4587 if (h
== INVALID_HANDLE_VALUE
) {
4588 WCMD_print_error ();
4589 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
4593 static const WCHAR fmt
[] = {'\n','%','1','\n','\n','\n','\0'};
4594 WCMD_output_stderr(fmt
, thisArg
);
4596 while (WCMD_ReadFile(h
, buffer
, ARRAY_SIZE(buffer
) - 1, &count
)) {
4597 if (count
== 0) break; /* ReadFile reports success on EOF! */
4599 WCMD_output_asis (buffer
);
4606 /****************************************************************************
4609 * Output either a file or stdin to screen in pages
4612 void WCMD_more (WCHAR
*args
) {
4617 WCHAR moreStrPage
[100];
4620 static const WCHAR moreStart
[] = {'-','-',' ','\0'};
4621 static const WCHAR moreFmt
[] = {'%','s',' ','-','-','\n','\0'};
4622 static const WCHAR moreFmt2
[] = {'%','s',' ','(','%','2','.','2','d','%','%',
4623 ')',' ','-','-','\n','\0'};
4624 static const WCHAR conInW
[] = {'C','O','N','I','N','$','\0'};
4626 /* Prefix the NLS more with '-- ', then load the text */
4628 lstrcpyW(moreStr
, moreStart
);
4629 LoadStringW(hinst
, WCMD_MORESTR
, &moreStr
[3], ARRAY_SIZE(moreStr
)-3);
4631 if (param1
[0] == 0x00) {
4633 /* Wine implements pipes via temporary files, and hence stdin is
4634 effectively reading from the file. This means the prompts for
4635 more are satisfied by the next line from the input (file). To
4636 avoid this, ensure stdin is to the console */
4637 HANDLE hstdin
= GetStdHandle(STD_INPUT_HANDLE
);
4638 HANDLE hConIn
= CreateFileW(conInW
, GENERIC_READ
| GENERIC_WRITE
,
4639 FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4640 FILE_ATTRIBUTE_NORMAL
, 0);
4641 WINE_TRACE("No parms - working probably in pipe mode\n");
4642 SetStdHandle(STD_INPUT_HANDLE
, hConIn
);
4644 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4645 once you get in this bit unless due to a pipe, it's going to end badly... */
4646 wsprintfW(moreStrPage
, moreFmt
, moreStr
);
4648 WCMD_enter_paged_mode(moreStrPage
);
4649 while (WCMD_ReadFile(hstdin
, buffer
, ARRAY_SIZE(buffer
)-1, &count
)) {
4650 if (count
== 0) break; /* ReadFile reports success on EOF! */
4652 WCMD_output_asis (buffer
);
4654 WCMD_leave_paged_mode();
4656 /* Restore stdin to what it was */
4657 SetStdHandle(STD_INPUT_HANDLE
, hstdin
);
4658 CloseHandle(hConIn
);
4662 BOOL needsPause
= FALSE
;
4664 /* Loop through all args */
4665 WINE_TRACE("Parms supplied - working through each file\n");
4666 WCMD_enter_paged_mode(moreStrPage
);
4669 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
4677 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, 100);
4678 WCMD_leave_paged_mode();
4679 WCMD_output_asis(moreStrPage
);
4680 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), buffer
, ARRAY_SIZE(buffer
), &count
);
4681 WCMD_enter_paged_mode(moreStrPage
);
4685 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
4686 h
= CreateFileW(thisArg
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
,
4687 FILE_ATTRIBUTE_NORMAL
, NULL
);
4688 if (h
== INVALID_HANDLE_VALUE
) {
4689 WCMD_print_error ();
4690 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), thisArg
);
4694 ULONG64 fileLen
= 0;
4695 WIN32_FILE_ATTRIBUTE_DATA fileInfo
;
4697 /* Get the file size */
4698 GetFileAttributesExW(thisArg
, GetFileExInfoStandard
, (void*)&fileInfo
);
4699 fileLen
= (((ULONG64
)fileInfo
.nFileSizeHigh
) << 32) + fileInfo
.nFileSizeLow
;
4702 while (WCMD_ReadFile(h
, buffer
, ARRAY_SIZE(buffer
)-1, &count
)) {
4703 if (count
== 0) break; /* ReadFile reports success on EOF! */
4707 /* Update % count (would be used in WCMD_output_asis as prompt) */
4708 wsprintfW(moreStrPage
, moreFmt2
, moreStr
, (int) min(99, (curPos
* 100)/fileLen
));
4710 WCMD_output_asis (buffer
);
4716 WCMD_leave_paged_mode();
4720 /****************************************************************************
4723 * Display verify flag.
4724 * FIXME: We don't actually do anything with the verify flag other than toggle
4728 void WCMD_verify (const WCHAR
*args
) {
4732 count
= lstrlenW(args
);
4734 if (verify_mode
) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), onW
);
4735 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT
), offW
);
4738 if (lstrcmpiW(args
, onW
) == 0) {
4742 else if (lstrcmpiW(args
, offW
) == 0) {
4743 verify_mode
= FALSE
;
4746 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR
));
4749 /****************************************************************************
4752 * Display version info.
4755 void WCMD_version (void) {
4757 WCMD_output_asis (version_string
);
4761 /****************************************************************************
4764 * Display volume information (set_label = FALSE)
4765 * Additionally set volume label (set_label = TRUE)
4766 * Returns 1 on success, 0 otherwise
4769 int WCMD_volume(BOOL set_label
, const WCHAR
*path
)
4771 DWORD count
, serial
;
4772 WCHAR string
[MAX_PATH
], label
[MAX_PATH
], curdir
[MAX_PATH
];
4776 status
= GetCurrentDirectoryW(ARRAY_SIZE(curdir
), curdir
);
4778 WCMD_print_error ();
4781 status
= GetVolumeInformationW(NULL
, label
, ARRAY_SIZE(label
), &serial
, NULL
, NULL
, NULL
, 0);
4784 static const WCHAR fmt
[] = {'%','s','\\','\0'};
4785 if ((path
[1] != ':') || (lstrlenW(path
) != 2)) {
4786 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR
));
4789 wsprintfW (curdir
, fmt
, path
);
4790 status
= GetVolumeInformationW(curdir
, label
, ARRAY_SIZE(label
), &serial
, NULL
, NULL
, NULL
, 0);
4793 WCMD_print_error ();
4796 if (label
[0] != '\0') {
4797 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL
),
4801 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL
),
4804 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO
),
4805 HIWORD(serial
), LOWORD(serial
));
4807 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT
));
4808 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE
), string
, ARRAY_SIZE(string
), &count
);
4810 string
[count
-1] = '\0'; /* ReadFile output is not null-terminated! */
4811 if (string
[count
-2] == '\r') string
[count
-2] = '\0'; /* Under Windoze we get CRLF! */
4814 if (!SetVolumeLabelW(curdir
, string
)) WCMD_print_error ();
4817 if (!SetVolumeLabelW(NULL
, string
)) WCMD_print_error ();
4823 /**************************************************************************
4826 * Exit either the process, or just this batch program
4830 void WCMD_exit (CMD_LIST
**cmdList
) {
4832 static const WCHAR parmB
[] = {'/','B','\0'};
4833 int rc
= wcstol(param1
, NULL
, 10); /* Note: wcstol of empty parameter is 0 */
4835 if (context
&& lstrcmpiW(quals
, parmB
) == 0) {
4837 context
-> skip_rest
= TRUE
;
4845 /*****************************************************************************
4848 * Lists or sets file associations (assoc = TRUE)
4849 * Lists or sets file types (assoc = FALSE)
4851 void WCMD_assoc (const WCHAR
*args
, BOOL assoc
) {
4854 DWORD accessOptions
= KEY_READ
;
4856 LONG rc
= ERROR_SUCCESS
;
4857 WCHAR keyValue
[MAXSTRING
];
4858 DWORD valueLen
= MAXSTRING
;
4860 static const WCHAR shOpCmdW
[] = {'\\','S','h','e','l','l','\\',
4861 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4863 /* See if parameter includes '=' */
4865 newValue
= wcschr(args
, '=');
4866 if (newValue
) accessOptions
|= KEY_WRITE
;
4868 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4869 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, nullW
, 0,
4870 accessOptions
, &key
) != ERROR_SUCCESS
) {
4871 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4875 /* If no parameters then list all associations */
4876 if (*args
== 0x00) {
4879 /* Enumerate all the keys */
4880 while (rc
!= ERROR_NO_MORE_ITEMS
) {
4881 WCHAR keyName
[MAXSTRING
];
4884 /* Find the next value */
4885 nameLen
= MAXSTRING
;
4886 rc
= RegEnumKeyExW(key
, index
++, keyName
, &nameLen
, NULL
, NULL
, NULL
, NULL
);
4888 if (rc
== ERROR_SUCCESS
) {
4890 /* Only interested in extension ones if assoc, or others
4892 if ((keyName
[0] == '.' && assoc
) ||
4893 (!(keyName
[0] == '.') && (!assoc
)))
4895 WCHAR subkey
[MAXSTRING
];
4896 lstrcpyW(subkey
, keyName
);
4897 if (!assoc
) lstrcatW(subkey
, shOpCmdW
);
4899 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
4901 valueLen
= ARRAY_SIZE(keyValue
);
4902 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
4903 WCMD_output_asis(keyName
);
4904 WCMD_output_asis(equalW
);
4905 /* If no default value found, leave line empty after '=' */
4906 if (rc
== ERROR_SUCCESS
) {
4907 WCMD_output_asis(keyValue
);
4909 WCMD_output_asis(newlineW
);
4910 RegCloseKey(readKey
);
4918 /* Parameter supplied - if no '=' on command line, it's a query */
4919 if (newValue
== NULL
) {
4921 WCHAR subkey
[MAXSTRING
];
4923 /* Query terminates the parameter at the first space */
4924 lstrcpyW(keyValue
, args
);
4925 space
= wcschr(keyValue
, ' ');
4926 if (space
) *space
=0x00;
4928 /* Set up key name */
4929 lstrcpyW(subkey
, keyValue
);
4930 if (!assoc
) lstrcatW(subkey
, shOpCmdW
);
4932 if (RegOpenKeyExW(key
, subkey
, 0, accessOptions
, &readKey
) == ERROR_SUCCESS
) {
4934 rc
= RegQueryValueExW(readKey
, NULL
, NULL
, NULL
, (LPBYTE
)keyValue
, &valueLen
);
4935 WCMD_output_asis(args
);
4936 WCMD_output_asis(equalW
);
4937 /* If no default value found, leave line empty after '=' */
4938 if (rc
== ERROR_SUCCESS
) WCMD_output_asis(keyValue
);
4939 WCMD_output_asis(newlineW
);
4940 RegCloseKey(readKey
);
4943 WCHAR msgbuffer
[MAXSTRING
];
4945 /* Load the translated 'File association not found' */
4947 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, ARRAY_SIZE(msgbuffer
));
4949 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, ARRAY_SIZE(msgbuffer
));
4951 WCMD_output_stderr(msgbuffer
, keyValue
);
4955 /* Not a query - it's a set or clear of a value */
4958 WCHAR subkey
[MAXSTRING
];
4960 /* Get pointer to new value */
4964 /* Set up key name */
4965 lstrcpyW(subkey
, args
);
4966 if (!assoc
) lstrcatW(subkey
, shOpCmdW
);
4968 /* If nothing after '=' then clear value - only valid for ASSOC */
4969 if (*newValue
== 0x00) {
4971 if (assoc
) rc
= RegDeleteKeyW(key
, args
);
4972 if (assoc
&& rc
== ERROR_SUCCESS
) {
4973 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args
));
4975 } else if (assoc
&& rc
!= ERROR_FILE_NOT_FOUND
) {
4980 WCHAR msgbuffer
[MAXSTRING
];
4982 /* Load the translated 'File association not found' */
4984 LoadStringW(hinst
, WCMD_NOASSOC
, msgbuffer
, ARRAY_SIZE(msgbuffer
));
4986 LoadStringW(hinst
, WCMD_NOFTYPE
, msgbuffer
, ARRAY_SIZE(msgbuffer
));
4988 WCMD_output_stderr(msgbuffer
, args
);
4992 /* It really is a set value = contents */
4994 rc
= RegCreateKeyExW(key
, subkey
, 0, NULL
, REG_OPTION_NON_VOLATILE
,
4995 accessOptions
, NULL
, &readKey
, NULL
);
4996 if (rc
== ERROR_SUCCESS
) {
4997 rc
= RegSetValueExW(readKey
, NULL
, 0, REG_SZ
,
4999 sizeof(WCHAR
) * (lstrlenW(newValue
) + 1));
5000 RegCloseKey(readKey
);
5003 if (rc
!= ERROR_SUCCESS
) {
5007 WCMD_output_asis(args
);
5008 WCMD_output_asis(equalW
);
5009 WCMD_output_asis(newValue
);
5010 WCMD_output_asis(newlineW
);
5020 /****************************************************************************
5023 * Colors the terminal screen.
5026 void WCMD_color (void) {
5028 CONSOLE_SCREEN_BUFFER_INFO consoleInfo
;
5029 HANDLE hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
);
5031 if (param1
[0] != 0x00 && lstrlenW(param1
) > 2) {
5032 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR
));
5036 if (GetConsoleScreenBufferInfo(hStdOut
, &consoleInfo
))
5042 screenSize
= consoleInfo
.dwSize
.X
* (consoleInfo
.dwSize
.Y
+ 1);
5047 /* Convert the color hex digits */
5048 if (param1
[0] == 0x00) {
5049 color
= defaultColor
;
5051 color
= wcstoul(param1
, NULL
, 16);
5054 /* Fail if fg == bg color */
5055 if (((color
& 0xF0) >> 4) == (color
& 0x0F)) {
5060 /* Set the current screen contents and ensure all future writes
5061 remain this color */
5062 FillConsoleOutputAttribute(hStdOut
, color
, screenSize
, topLeft
, &screenSize
);
5063 SetConsoleTextAttribute(hStdOut
, color
);
5067 /****************************************************************************
5071 void WCMD_mklink(WCHAR
*args
)
5076 BOOL junction
= FALSE
;
5079 WCHAR file1
[MAX_PATH
];
5080 WCHAR file2
[MAX_PATH
];
5081 static const WCHAR optD
[] = {'/', 'D', '\0'};
5082 static const WCHAR optH
[] = {'/', 'H', '\0'};
5083 static const WCHAR optJ
[] = {'/', 'J', '\0'};
5085 if (param1
[0] == 0x00 || param2
[0] == 0x00) {
5086 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG
));
5093 WCHAR
*thisArg
= WCMD_parameter (args
, argno
++, &argN
, FALSE
, FALSE
);
5097 WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg
));
5099 if(lstrcmpiW(thisArg
, optD
) == 0)
5101 else if(lstrcmpiW(thisArg
, optH
) == 0)
5103 else if(lstrcmpiW(thisArg
, optJ
) == 0)
5107 lstrcpyW(file1
, thisArg
);
5109 lstrcpyW(file2
, thisArg
);
5114 ret
= CreateHardLinkW(file1
, file2
, NULL
);
5116 ret
= CreateSymbolicLinkW(file1
, file2
, isdir
);
5118 WINE_TRACE("Juction links currently not supported.\n");
5121 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL
), file1
);