Release 3.5.
[wine.git] / programs / cmd / builtins.c
blobec66cd57243401478e74bfa15f2bf3c61502d218
1 /*
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
23 * FIXME:
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
31 #include "wcmd.h"
32 #include <shellapi.h>
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'},
53 {'C','D','\0'},
54 {'C','H','D','I','R','\0'},
55 {'C','L','S','\0'},
56 {'C','O','P','Y','\0'},
57 {'C','T','T','Y','\0'},
58 {'D','A','T','E','\0'},
59 {'D','E','L','\0'},
60 {'D','I','R','\0'},
61 {'E','C','H','O','\0'},
62 {'E','R','A','S','E','\0'},
63 {'F','O','R','\0'},
64 {'G','O','T','O','\0'},
65 {'H','E','L','P','\0'},
66 {'I','F','\0'},
67 {'L','A','B','E','L','\0'},
68 {'M','D','\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'},
74 {'R','E','M','\0'},
75 {'R','E','N','\0'},
76 {'R','E','N','A','M','E','\0'},
77 {'R','D','\0'},
78 {'R','M','D','I','R','\0'},
79 {'S','E','T','\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'},
86 {'V','E','R','\0'},
87 {'V','O','L','\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
134 int precedence;
135 WCHAR op;
136 struct _OPSTACK *next;
137 } OPSTACK;
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
143 BOOL isnum;
144 WCHAR *variable;
145 int value;
146 struct _VARSTACK *next;
147 } VARSTACK;
149 /* This maintains a mapping between the calculated operator and the
150 single character representation for the assignment operators. */
151 static struct
153 WCHAR op;
154 WCHAR calculatedop;
155 } calcassignments[] =
157 {'*', OP_ASSSIGNMUL},
158 {'/', OP_ASSSIGNDIV},
159 {'%', OP_ASSSIGNMOD},
160 {'+', OP_ASSSIGNADD},
161 {'-', OP_ASSSIGNSUB},
162 {'&', OP_ASSSIGNAND},
163 {'^', OP_ASSSIGNNOT},
164 {'|', OP_ASSSIGNOR},
165 {'<', OP_ASSSIGNSHL},
166 {'>', OP_ASSSIGNSHR},
167 {' ',' '}
170 /**************************************************************************
171 * WCMD_ask_confirm
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
177 * set to TRUE
180 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
181 BOOL *optionAll) {
183 UINT msgid;
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'};
190 DWORD count = 0;
192 /* Load the translated valid answers */
193 if (showSureText)
194 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
195 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
196 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
197 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
198 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
199 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
201 /* Loop waiting on a valid answer */
202 if (optionAll)
203 *optionAll = FALSE;
204 while (1)
206 WCMD_output_asis (message);
207 if (showSureText)
208 WCMD_output_asis (confirm);
209 WCMD_output_asis (options);
210 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
211 answer[0] = toupperW(answer[0]);
212 if (answer[0] == Ybuffer[0])
213 return TRUE;
214 if (answer[0] == Nbuffer[0])
215 return FALSE;
216 if (optionAll && answer[0] == Abuffer[0])
218 *optionAll = TRUE;
219 return TRUE;
224 /****************************************************************************
225 * WCMD_clear_screen
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))
239 COORD topLeft;
240 DWORD screenSize, written;
242 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
244 topLeft.X = 0;
245 topLeft.Y = 0;
246 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
247 FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
248 SetConsoleCursorPosition(hStdOut, topLeft);
252 /****************************************************************************
253 * WCMD_change_tty
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 /****************************************************************************
265 * WCMD_choice
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};
275 WCHAR answer[16];
276 WCHAR buffer[16];
277 WCHAR *ptr = NULL;
278 WCHAR *opt_c = NULL;
279 WCHAR *my_command = NULL;
280 WCHAR opt_default = 0;
281 DWORD opt_timeout = 0;
282 DWORD count;
283 DWORD oldmode;
284 BOOL have_console;
285 BOOL opt_n = FALSE;
286 BOOL opt_s = FALSE;
288 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
289 errorlevel = 0;
291 my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
293 ptr = WCMD_skip_leading_spaces(my_command);
294 while (*ptr == '/') {
295 switch (toupperW(ptr[1])) {
296 case 'C':
297 ptr += 2;
298 /* the colon is optional */
299 if (*ptr == ':')
300 ptr++;
302 if (!*ptr || isspaceW(*ptr)) {
303 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
304 heap_free(my_command);
305 return;
308 /* remember the allowed keys (overwrite previous /C option) */
309 opt_c = ptr;
310 while (*ptr && (!isspaceW(*ptr)))
311 ptr++;
313 if (*ptr) {
314 /* terminate allowed chars */
315 *ptr = 0;
316 ptr = WCMD_skip_leading_spaces(&ptr[1]);
318 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
319 break;
321 case 'N':
322 opt_n = TRUE;
323 ptr = WCMD_skip_leading_spaces(&ptr[2]);
324 break;
326 case 'S':
327 opt_s = TRUE;
328 ptr = WCMD_skip_leading_spaces(&ptr[2]);
329 break;
331 case 'T':
332 ptr = &ptr[2];
333 /* the colon is optional */
334 if (*ptr == ':')
335 ptr++;
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);
342 return;
344 ptr++;
346 count = 0;
347 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
348 count++;
349 ptr++;
352 answer[count] = 0;
353 opt_timeout = atoiW(answer);
355 ptr = WCMD_skip_leading_spaces(ptr);
356 break;
358 default:
359 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
360 heap_free(my_command);
361 return;
365 if (opt_timeout)
366 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
368 if (have_console)
369 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
371 /* use default keys, when needed: localized versions of "Y"es and "No" */
372 if (!opt_c) {
373 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
374 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
375 opt_c = buffer;
376 buffer[2] = 0;
379 /* print the question, when needed */
380 if (*ptr)
381 WCMD_output_asis(ptr);
383 if (!opt_s) {
384 struprW(opt_c);
385 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
388 if (!opt_n) {
389 /* print a list of all allowed answers inside brackets */
390 WCMD_output_asis(bracket_open);
391 ptr = opt_c;
392 answer[1] = 0;
393 while ((answer[0] = *ptr++)) {
394 WCMD_output_asis(answer);
395 if (*ptr)
396 WCMD_output_asis(commaW);
398 WCMD_output_asis(bracket_close);
401 while (TRUE) {
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);
407 if (!opt_s)
408 answer[0] = toupperW(answer[0]);
410 ptr = strchrW(opt_c, answer[0]);
411 if (ptr) {
412 WCMD_output_asis(answer);
413 WCMD_output_asis(newlineW);
414 if (have_console)
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);
420 return;
422 else
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 /****************************************************************************
432 * WCMD_AppendEOF
434 * Adds an EOF onto the end of a file
435 * Returns TRUE on success
437 static BOOL WCMD_AppendEOF(WCHAR *filename)
439 HANDLE h;
440 DWORD bytes_written;
442 char eof = '\x1a';
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());
450 return FALSE;
451 } else {
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());
455 CloseHandle(h);
456 return FALSE;
458 CloseHandle(h);
460 return TRUE;
463 /****************************************************************************
464 * WCMD_IsSameFile
466 * Checks if the two paths reference to the same file
468 static BOOL WCMD_IsSameFile(const WCHAR *name1, const WCHAR *name2)
470 BOOL ret = FALSE;
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))
476 goto end;
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))
480 goto end;
482 ret = info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
483 && info1.nFileIndexHigh == info2.nFileIndexHigh
484 && info1.nFileIndexLow == info2.nFileIndexLow;
485 end:
486 if (file1 != INVALID_HANDLE_VALUE)
487 CloseHandle(file1);
488 if (file2 != INVALID_HANDLE_VALUE)
489 CloseHandle(file2);
490 return ret;
493 /****************************************************************************
494 * WCMD_ManualCopy
496 * Copies from a file
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)
503 HANDLE in,out;
504 BOOL ok;
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());
514 return FALSE;
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());
522 CloseHandle(in);
523 return FALSE;
526 /* Move to end of destination if we are going to append to it */
527 if (append) {
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);
537 if (ok) {
539 /* Stop at first EOF */
540 if (ascii) {
541 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
542 if (ptr) bytesread = (ptr - buffer);
545 if (bytesread) {
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());
552 } else {
553 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
554 wine_dbgstr_w(srcname), GetLastError());
556 } while (ok && bytesread > 0);
558 CloseHandle(out);
559 CloseHandle(in);
560 return ok;
563 /****************************************************************************
564 * WCMD_copy
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;
590 WCHAR *thisparam;
591 int argno = 0;
592 WCHAR *rawarg;
593 WIN32_FIND_DATAW fd;
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? */
603 BOOL status;
604 WCHAR copycmd[4];
605 DWORD len;
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;
612 BOOL concatenate;
613 WCHAR *name;
614 int binarycopy;
615 } COPY_FILES;
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! */
623 errorlevel = 0;
625 /* If no args supplied at all, report an error */
626 if (param1[0] == 0x00) {
627 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
628 errorlevel = 1;
629 return;
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)) {
637 WCHAR *pos1, *pos2;
638 BOOL inquotes;
640 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
642 /* Handle switches */
643 if (*thisparam == '/') {
644 while (*thisparam == '/') {
645 thisparam++;
646 if (toupperW(*thisparam) == 'D') {
647 opt_d = TRUE;
648 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
649 } else if (toupperW(*thisparam) == 'Y') {
650 opt_y = TRUE;
651 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
652 opt_noty = TRUE;
653 } else if (toupperW(*thisparam) == 'V') {
654 opt_v = TRUE;
655 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
656 } else if (toupperW(*thisparam) == 'N') {
657 opt_n = TRUE;
658 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
659 } else if (toupperW(*thisparam) == 'Z') {
660 opt_z = TRUE;
661 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
662 } else if (toupperW(*thisparam) == 'A') {
663 if (binarymode != 0) {
664 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 (toupperW(*thisparam) == 'B') {
675 if (binarymode != 1) {
676 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;
686 } else {
687 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
689 thisparam++;
692 /* This parameter was purely switches, get the next one */
693 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
694 continue;
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
701 +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));
708 errorlevel = 1;
709 goto exitreturn;
710 } else {
711 concatnextfilename = TRUE;
712 anyconcats = TRUE;
715 /* Move to next thing to process */
716 thisparam++;
717 if (*thisparam == 0x00)
718 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
719 continue;
722 /* We have found something to process - build a COPY_FILE block to store it */
723 thiscopy = heap_alloc(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 = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
734 thiscopy->name = heap_alloc(len*sizeof(WCHAR));
735 memset(thiscopy->name, 0x00, len);
737 pos1 = thisparam;
738 pos2 = thiscopy->name;
739 inquotes = FALSE;
740 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
741 if (*pos1 == '"') {
742 inquotes = !inquotes;
743 pos1++;
744 } else *pos2++ = *pos1++;
746 *pos2 = 0;
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;
760 } else {
761 /* We have processed sources and destinations and still found more to do - invalid */
762 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
763 errorlevel = 1;
764 goto exitreturn;
766 concatnextfilename = FALSE;
768 /* We either need to process the rest of the parameter or move to the next */
769 if (*pos1 == '/' || *pos1 == '+') {
770 thisparam = pos1;
771 continue;
772 } else {
773 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
777 /* Ensure we have at least one source file */
778 if (!sourcelist) {
779 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
780 errorlevel = 1;
781 goto exitreturn;
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;
789 else {
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, sizeof(copycmd)/sizeof(WCHAR));
797 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
798 if (!lstrcmpiW (copycmd, parmY))
799 prompt = FALSE;
800 else if (!lstrcmpiW (copycmd, parmNoY))
801 prompt = TRUE;
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 strcpyW(destname, dotW);
811 strcatW(destname, slashW);
813 destination = heap_alloc(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;
821 } else {
822 WCHAR *filenamepart;
823 DWORD attributes;
825 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
827 /* Convert to fully qualified path/filename */
828 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), 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 )) strcatW(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.
845 Note that if the
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. */
850 if (anyconcats) {
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 (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
861 anyconcats = TRUE; /* We really are concatenating to a single file */
862 if (destination->binarycopy == -1) {
863 destination->binarycopy = 0;
865 } else {
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 (strncmpW(destination->name, deviceW, strlenW(deviceW)) == 0) {
880 WINE_TRACE("Destination is a device\n");
881 dstisdevice = TRUE;
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;
894 prevcopy = NULL;
896 while (thiscopy != NULL) {
898 WCHAR srcpath[MAX_PATH];
899 const WCHAR *srcname;
900 WCHAR *filenamepart;
901 DWORD attributes;
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, sizeof(srcpath)/sizeof(WCHAR), 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 strcatW(thiscopy->name, starW);
920 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
921 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
923 } else if ((strpbrkW(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 strcatW(thiscopy->name, slashstarW);
930 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), 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 (strncmpW(srcpath, deviceW, strlenW(deviceW)) == 0) {
939 WINE_TRACE("Source is a device\n");
940 srcisdevice = TRUE;
941 srcname = &srcpath[4]; /* After the \\.\ prefix */
942 } else {
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) {
953 do {
954 WCHAR outname[MAX_PATH];
955 BOOL overwrite;
956 BOOL appendtofirstfile = FALSE;
958 /* Skip . and .., and directories */
959 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
960 WINE_TRACE("Skipping directories\n");
961 } else {
963 /* Build final destination name */
964 strcpyW(outname, destination->name);
965 if (destisdirectory || appendfirstsource) strcatW(outname, srcname);
967 /* Build source name */
968 if (!srcisdevice) strcpyW(filenamepart, srcname);
970 /* Do we just overwrite (we do if we are writing to a device) */
971 overwrite = !prompt;
972 if (dstisdevice || (anyconcats && writtenoneconcat)) {
973 overwrite = TRUE;
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) {
987 overwrite = TRUE;
988 } else if (!overwrite) {
989 DWORD attributes = GetFileAttributesW(outname);
990 if (attributes != INVALID_FILE_ATTRIBUTES) {
991 WCHAR* question;
992 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
993 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
994 LocalFree(question);
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 */
1009 if (overwrite) {
1010 if (anyconcats && WCMD_IsSameFile(srcpath, outname)) {
1011 /* Silently skip if the destination file is also a source file */
1012 status = TRUE;
1013 } else if (anyconcats && writtenoneconcat) {
1014 if (thiscopy->binarycopy) {
1015 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
1016 } else {
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);
1023 } else {
1024 status = CopyFileW(srcpath, outname, FALSE);
1026 if (!status) {
1027 WCMD_print_error ();
1028 errorlevel = 1;
1029 } else {
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 ();
1040 errorlevel = 1;
1046 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1047 if (!srcisdevice) FindClose (hff);
1048 } else {
1049 /* Error if the first file was not found */
1050 if (!anyconcats || !writtenoneconcat) {
1051 WCMD_print_error ();
1052 errorlevel = 1;
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 ();
1064 errorlevel = 1;
1068 /* Exit out of the routine, freeing any remaining allocated memory */
1069 exitreturn:
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 */
1081 if (destination) {
1082 heap_free(destination->name);
1083 heap_free(destination);
1086 return;
1089 /****************************************************************************
1090 * WCMD_create_dir
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)
1099 WCHAR *p, *start;
1101 /* don't mess with drive letter portion of path, if any */
1102 start = path;
1103 if (path[1] == ':')
1104 start = path+2;
1106 /* Strip trailing slashes. */
1107 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
1108 *p = 0;
1110 /* Step through path, creating intermediate directories as needed. */
1111 /* First component includes drive letter, if any. */
1112 p = start;
1113 for (;;) {
1114 DWORD rv;
1115 /* Skip to end of component */
1116 while (*p == '\\') p++;
1117 while (*p && *p != '\\') p++;
1118 if (!*p) {
1119 /* path is now the original full path */
1120 return CreateDirectoryW(path, NULL);
1122 /* Truncate path, create intermediate directory, and restore path */
1123 *p = 0;
1124 rv = CreateDirectoryW(path, NULL);
1125 *p = '\\';
1126 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1127 return FALSE;
1129 /* notreached */
1130 return FALSE;
1133 void WCMD_create_dir (WCHAR *args) {
1134 int argno = 0;
1135 WCHAR *argN = args;
1137 if (param1[0] == 0x00) {
1138 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1139 return;
1141 /* Loop through all args */
1142 while (TRUE) {
1143 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1144 if (!argN) break;
1145 if (!create_full_path(thisArg)) {
1146 WCMD_print_error ();
1147 errorlevel = 1;
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'};
1158 WCHAR *p;
1160 /* both are strictly 'out' parameters */
1161 *wantSet=0;
1162 *wantClear=0;
1164 /* For each /A argument */
1165 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1166 /* Skip /A itself */
1167 p += 2;
1169 /* Skip optional : */
1170 if (*p == ':') p++;
1172 /* For each of the attribute specifier chars to this /A option */
1173 for (; *p != 0 && *p != '/'; p++) {
1174 BOOL negate = FALSE;
1175 DWORD mask = 0;
1177 if (*p == '-') {
1178 negate=TRUE;
1179 p++;
1182 /* Convert the attribute specifier to a bit in one of the masks */
1183 switch (*p) {
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;
1188 default:
1189 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1191 if (negate)
1192 *wantClear |= mask;
1193 else
1194 *wantSet |= mask;
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 ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1211 static const WCHAR anyExt[]= {'.','*','\0'};
1212 WCHAR drive[10];
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, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1220 WCMD_splitpath(fpath, drive, dir, fname, ext);
1222 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1223 if ((strcmpW(fname, starW) == 0) &&
1224 (*ext == 0x00 || (strcmpW(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 */
1230 *pPrompted = TRUE;
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) */
1238 return TRUE;
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'};
1251 DWORD wanted_attrs;
1252 DWORD unwanted_attrs;
1253 BOOL found = FALSE;
1254 WCHAR argCopy[MAX_PATH];
1255 WIN32_FIND_DATAW fd;
1256 HANDLE hff;
1257 WCHAR fpath[MAX_PATH];
1258 WCHAR *p;
1259 BOOL handleParm = TRUE;
1261 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1263 strcpyW(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 *.* */
1269 return FALSE;
1272 /* First, try to delete in the current directory */
1273 hff = FindFirstFileW(argCopy, &fd);
1274 if (hff == INVALID_HANDLE_VALUE) {
1275 handleParm = FALSE;
1276 } else {
1277 found = TRUE;
1280 /* Support del <dirname> by just deleting all files dirname\* */
1281 if (handleParm
1282 && (strchrW(argCopy,'*') == NULL)
1283 && (strchrW(argCopy,'?') == NULL)
1284 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1286 WCHAR modifiedParm[MAX_PATH];
1287 static const WCHAR slashStar[] = {'\\','*','\0'};
1289 strcpyW(modifiedParm, argCopy);
1290 strcatW(modifiedParm, slashStar);
1291 FindClose(hff);
1292 found = TRUE;
1293 WCMD_delete_one(modifiedParm);
1295 } else if (handleParm) {
1297 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1298 strcpyW (fpath, argCopy);
1299 do {
1300 p = strrchrW (fpath, '\\');
1301 if (p != NULL) {
1302 *++p = '\0';
1303 strcatW (fpath, fd.cFileName);
1305 else strcpyW (fpath, fd.cFileName);
1306 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1307 BOOL ok;
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 && strstrW (quals, parmP) != NULL) {
1315 WCHAR* question;
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 */
1324 if (ok) {
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 strstrW (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);
1339 FindClose (hff);
1342 /* Now recurse into all subdirectories handling the parameter in the same way */
1343 if (strstrW (quals, parmS) != NULL) {
1345 WCHAR thisDir[MAX_PATH];
1346 int cPos;
1348 WCHAR drive[10];
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, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1355 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1357 strcpyW(thisDir, drive);
1358 strcatW(thisDir, dir);
1359 cPos = strlenW(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;
1376 do {
1377 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1378 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1379 (strcmpW(fd.cFileName, dotW) != 0)) {
1381 DIRECTORY_STACK *nextDir;
1382 WCHAR subParm[MAX_PATH];
1384 /* Work out search parameter in sub dir */
1385 strcpyW (subParm, thisDir);
1386 strcatW (subParm, fd.cFileName);
1387 strcatW (subParm, slashW);
1388 strcatW (subParm, fname);
1389 strcatW (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_alloc(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);
1401 FindClose (hff);
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);
1411 heap_free(allDirs);
1412 allDirs = tempDir;
1417 return found;
1420 /****************************************************************************
1421 * WCMD_delete
1423 * Delete a file or wildcarded set.
1425 * Note on /A:
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
1430 * non-hidden files
1433 BOOL WCMD_delete (WCHAR *args) {
1434 int argno;
1435 WCHAR *argN;
1436 BOOL argsProcessed = FALSE;
1437 BOOL foundAny = FALSE;
1439 errorlevel = 0;
1441 for (argno=0; ; argno++) {
1442 BOOL found;
1443 WCHAR *thisArg;
1445 argN = NULL;
1446 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1447 if (!argN)
1448 break; /* no more parameters */
1449 if (argN[0] == '/')
1450 continue; /* skip options */
1452 argsProcessed = TRUE;
1453 found = WCMD_delete_one(thisArg);
1454 if (!found)
1455 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1456 foundAny |= found;
1459 /* Handle no valid args */
1460 if (!argsProcessed)
1461 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1463 return foundAny;
1467 * WCMD_strtrim
1469 * Returns a trimmed version of s with all leading and trailing whitespace removed
1470 * Pre: s non NULL
1473 static WCHAR *WCMD_strtrim(const WCHAR *s)
1475 DWORD len = strlenW(s);
1476 const WCHAR *start = s;
1477 WCHAR* result;
1479 result = heap_alloc((len + 1) * sizeof(WCHAR));
1481 while (isspaceW(*start)) start++;
1482 if (*start) {
1483 const WCHAR *end = s + len - 1;
1484 while (end > start && isspaceW(*end)) end--;
1485 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1486 result[end - start + 1] = '\0';
1487 } else {
1488 result[0] = '\0';
1491 return result;
1494 /****************************************************************************
1495 * WCMD_echo
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)
1503 int count;
1504 const WCHAR *origcommand = args;
1505 WCHAR *trimmed;
1507 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1508 || args[0]==':' || args[0]==';' || args[0]=='/')
1509 args++;
1511 trimmed = WCMD_strtrim(args);
1512 if (!trimmed) return;
1514 count = strlenW(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);
1519 heap_free(trimmed);
1520 return;
1523 if (lstrcmpiW(trimmed, onW) == 0)
1524 echo_mode = TRUE;
1525 else if (lstrcmpiW(trimmed, offW) == 0)
1526 echo_mode = FALSE;
1527 else {
1528 WCMD_output_asis (args);
1529 WCMD_output_asis (newlineW);
1531 heap_free(trimmed);
1534 /*****************************************************************************
1535 * WCMD_part_execute
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)\n", cmdList, wine_dbgstr_w(firstcmd),
1548 executecmds);
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);
1557 heap_free(command);
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 */
1565 if (*cmdList) {
1566 BOOL processThese = executecmds;
1568 while (*cmdList) {
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)\n",
1575 *cmdList,
1576 (*cmdList)->prevDelim,
1577 (*cmdList)->bracketDepth, myDepth);
1579 /* Execute any statements appended to the line */
1580 /* FIXME: Only if previous call worked for && or failed for || */
1581 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1582 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1583 if (processThese && (*cmdList)->command) {
1584 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1585 cmdList, FALSE);
1587 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1589 /* Execute any appended to the statement with (...) */
1590 } else if ((*cmdList)->bracketDepth > myDepth) {
1591 if (processThese) {
1592 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1593 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1595 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1597 /* End of the command - does 'ELSE ' follow as the next command? */
1598 } else {
1599 if (isIF
1600 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1601 (*cmdList)->command)) {
1603 /* Swap between if and else processing */
1604 processThese = !executecmds;
1606 /* Process the ELSE part */
1607 if (processThese) {
1608 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1609 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1611 /* Skip leading whitespace between condition and the command */
1612 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1613 if (*cmd) {
1614 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1617 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1618 } else if (!processThese) {
1619 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1620 WINE_TRACE("Ignore the next command as well (next = %p)\n", *cmdList);
1621 } else {
1622 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1623 break;
1628 return;
1631 /*****************************************************************************
1632 * WCMD_parse_forf_options
1634 * Parses the for /f 'options', extracting the values and validating the
1635 * keywords. Note all keywords are optional.
1636 * Parameters:
1637 * options [I] The unparsed parameter string
1638 * eol [O] Set to the comment character (eol=x)
1639 * skip [O] Set to the number of lines to skip (skip=xx)
1640 * delims [O] Set to the token delimiters (delims=)
1641 * tokens [O] Set to the requested tokens, as provided (tokens=)
1642 * usebackq [O] Set to TRUE if usebackq found
1644 * Returns TRUE on success, FALSE on syntax error
1647 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1648 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1651 WCHAR *pos = options;
1652 int len = strlenW(pos);
1653 static const WCHAR eolW[] = {'e','o','l','='};
1654 static const WCHAR skipW[] = {'s','k','i','p','='};
1655 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1656 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1657 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1658 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1659 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1661 /* Initialize to defaults */
1662 strcpyW(delims, forf_defaultdelims);
1663 strcpyW(tokens, forf_defaulttokens);
1664 *eol = 0;
1665 *skip = 0;
1666 *usebackq = FALSE;
1668 /* Strip (optional) leading and trailing quotes */
1669 if ((*pos == '"') && (pos[len-1] == '"')) {
1670 pos[len-1] = 0;
1671 pos++;
1674 /* Process each keyword */
1675 while (pos && *pos) {
1676 if (*pos == ' ' || *pos == '\t') {
1677 pos++;
1679 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1680 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1681 pos, sizeof(eolW)/sizeof(WCHAR),
1682 eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1683 *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1684 pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1685 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1687 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1688 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1689 pos, sizeof(skipW)/sizeof(WCHAR),
1690 skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1691 WCHAR *nextchar = NULL;
1692 pos = pos + sizeof(skipW)/sizeof(WCHAR);
1693 *skip = strtoulW(pos, &nextchar, 0);
1694 WINE_TRACE("Found skip as %d lines\n", *skip);
1695 pos = nextchar;
1697 /* Save if usebackq semantics are in effect */
1698 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1699 pos, sizeof(usebackqW)/sizeof(WCHAR),
1700 usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1701 *usebackq = TRUE;
1702 pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1703 WINE_TRACE("Found usebackq\n");
1705 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1706 if you finish the optionsroot string with delims= otherwise the space is
1707 just a token delimiter! */
1708 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1709 pos, sizeof(delimsW)/sizeof(WCHAR),
1710 delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1711 int i=0;
1713 pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1714 while (*pos && *pos != ' ') {
1715 delims[i++] = *pos;
1716 pos++;
1718 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1719 delims[i++] = 0; /* Null terminate the delims */
1720 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1722 /* Save the tokens being requested */
1723 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1724 pos, sizeof(tokensW)/sizeof(WCHAR),
1725 tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1726 int i=0;
1728 pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1729 while (*pos && *pos != ' ') {
1730 tokens[i++] = *pos;
1731 pos++;
1733 tokens[i++] = 0; /* Null terminate the tokens */
1734 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1736 } else {
1737 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1738 return FALSE;
1741 return TRUE;
1744 /*****************************************************************************
1745 * WCMD_add_dirstowalk
1747 * When recursing through directories (for /r), we need to add to the list of
1748 * directories still to walk, any subdirectories of the one we are processing.
1750 * Parameters
1751 * options [I] The remaining list of directories still to process
1753 * Note this routine inserts the subdirectories found between the entry being
1754 * processed, and any other directory still to be processed, mimicking what
1755 * Windows does
1757 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1758 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1759 WCHAR fullitem[MAX_PATH];
1760 WIN32_FIND_DATAW fd;
1761 HANDLE hff;
1763 /* Build a generic search and add all directories on the list of directories
1764 still to walk */
1765 strcpyW(fullitem, dirsToWalk->dirName);
1766 strcatW(fullitem, slashstarW);
1767 hff = FindFirstFileW(fullitem, &fd);
1768 if (hff != INVALID_HANDLE_VALUE) {
1769 do {
1770 WINE_TRACE("Looking for subdirectories\n");
1771 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1772 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1773 (strcmpW(fd.cFileName, dotW) != 0))
1775 /* Allocate memory, add to list */
1776 DIRECTORY_STACK *toWalk = heap_alloc(sizeof(DIRECTORY_STACK));
1777 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1778 toWalk->next = remainingDirs->next;
1779 remainingDirs->next = toWalk;
1780 remainingDirs = toWalk;
1781 toWalk->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1782 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1783 strcatW(toWalk->dirName, slashW);
1784 strcatW(toWalk->dirName, fd.cFileName);
1785 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1786 toWalk, toWalk->next);
1788 } while (FindNextFileW(hff, &fd) != 0);
1789 WINE_TRACE("Finished adding all subdirectories\n");
1790 FindClose (hff);
1794 /**************************************************************************
1795 * WCMD_for_nexttoken
1797 * Parse the token= line, identifying the next highest number not processed
1798 * so far. Count how many tokens are referred (including duplicates) and
1799 * optionally return that, plus optionally indicate if the tokens= line
1800 * ends in a star.
1802 * Parameters:
1803 * lasttoken [I] - Identifies the token index of the last one
1804 * returned so far (-1 used for first loop)
1805 * tokenstr [I] - The specified tokens= line
1806 * firstCmd [O] - Optionally indicate how many tokens are listed
1807 * doAll [O] - Optionally indicate if line ends with *
1808 * duplicates [O] - Optionally indicate if there is any evidence of
1809 * overlaying tokens in the string
1810 * Note the caller should keep a running track of duplicates as the tokens
1811 * are recursively passed. If any have duplicates, then the * token should
1812 * not be honoured.
1814 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1815 int *totalfound, BOOL *doall,
1816 BOOL *duplicates)
1818 WCHAR *pos = tokenstr;
1819 int nexttoken = -1;
1821 if (totalfound) *totalfound = 0;
1822 if (doall) *doall = FALSE;
1823 if (duplicates) *duplicates = FALSE;
1825 WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken,
1826 wine_dbgstr_w(tokenstr), nexttoken);
1828 /* Loop through the token string, parsing it. Valid syntax is:
1829 token=m or x-y with comma delimiter and optionally * to finish*/
1830 while (*pos) {
1831 int nextnumber1, nextnumber2 = -1;
1832 WCHAR *nextchar;
1834 /* Get the next number */
1835 nextnumber1 = strtoulW(pos, &nextchar, 10);
1837 /* If it is followed by a minus, it's a range, so get the next one as well */
1838 if (*nextchar == '-') {
1839 nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1841 /* We want to return the lowest number that is higher than lasttoken
1842 but only if range is positive */
1843 if (nextnumber2 >= nextnumber1 &&
1844 lasttoken < nextnumber2) {
1846 int nextvalue;
1847 if (nexttoken == -1) {
1848 nextvalue = max(nextnumber1, (lasttoken+1));
1849 } else {
1850 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1853 /* Flag if duplicates identified */
1854 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1856 nexttoken = nextvalue;
1859 /* Update the running total for the whole range */
1860 if (nextnumber2 >= nextnumber1 && totalfound) {
1861 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1864 } else {
1865 if (totalfound) (*totalfound)++;
1867 /* See if the number found is one we have already seen */
1868 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1870 /* We want to return the lowest number that is higher than lasttoken */
1871 if (lasttoken < nextnumber1 &&
1872 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1873 nexttoken = nextnumber1;
1878 /* Remember if it is followed by a star, and if it is indicate a need to
1879 show all tokens, unless a duplicate has been found */
1880 if (*nextchar == '*') {
1881 if (doall) *doall = TRUE;
1882 if (totalfound) (*totalfound)++;
1885 /* Step on to the next character */
1886 pos = nextchar;
1887 if (*pos) pos++;
1890 /* Return result */
1891 if (nexttoken == -1) nexttoken = lasttoken;
1892 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1893 if (totalfound) WINE_TRACE("Found total tokens in total %d\n", *totalfound);
1894 if (doall && *doall) WINE_TRACE("Request for all tokens found\n");
1895 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1896 return nexttoken;
1899 /**************************************************************************
1900 * WCMD_parse_line
1902 * When parsing file or string contents (for /f), once the string to parse
1903 * has been identified, handle the various options and call the do part
1904 * if appropriate.
1906 * Parameters:
1907 * cmdStart [I] - Identifies the list of commands making up the
1908 * for loop body (especially if brackets in use)
1909 * firstCmd [I] - The textual start of the command after the DO
1910 * which is within the first item of cmdStart
1911 * cmdEnd [O] - Identifies where to continue after the DO
1912 * variable [I] - The variable identified on the for line
1913 * buffer [I] - The string to parse
1914 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1915 * forf_skip [I/O] - How many lines to skip first
1916 * forf_eol [I] - The 'end of line' (comment) character
1917 * forf_delims [I] - The delimiters to use when breaking the string apart
1918 * forf_tokens [I] - The tokens to use when breaking the string apart
1920 static void WCMD_parse_line(CMD_LIST *cmdStart,
1921 const WCHAR *firstCmd,
1922 CMD_LIST **cmdEnd,
1923 const WCHAR variable,
1924 WCHAR *buffer,
1925 BOOL *doExecuted,
1926 int *forf_skip,
1927 WCHAR forf_eol,
1928 WCHAR *forf_delims,
1929 WCHAR *forf_tokens) {
1931 WCHAR *parm;
1932 FOR_CONTEXT oldcontext;
1933 int varidx, varoffset;
1934 int nexttoken, lasttoken = -1;
1935 BOOL starfound = FALSE;
1936 BOOL thisduplicate = FALSE;
1937 BOOL anyduplicates = FALSE;
1938 int totalfound;
1940 /* Skip lines if requested */
1941 if (*forf_skip) {
1942 (*forf_skip)--;
1943 return;
1946 /* Save away any existing for variable context (e.g. nested for loops) */
1947 oldcontext = forloopcontext;
1949 /* Extract the parameters based on the tokens= value (There will always
1950 be some value, as if it is not supplied, it defaults to tokens=1).
1951 Rough logic:
1952 Count how many tokens are named in the line, identify the lowest
1953 Empty (set to null terminated string) that number of named variables
1954 While lasttoken != nextlowest
1955 %letter = parameter number 'nextlowest'
1956 letter++ (if >26 or >52 abort)
1957 Go through token= string finding next lowest number
1958 If token ends in * set %letter = raw position of token(nextnumber+1)
1960 lasttoken = -1;
1961 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1962 NULL, &thisduplicate);
1963 varidx = FOR_VAR_IDX(variable);
1965 /* Empty out variables */
1966 for (varoffset=0;
1967 varidx >= 0 && varoffset<totalfound && ((varidx+varoffset)%26);
1968 varoffset++) {
1969 forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
1970 /* Stop if we walk beyond z or Z */
1971 if (((varidx+varoffset) % 26) == 0) break;
1974 /* Loop extracting the tokens */
1975 varoffset = 0;
1976 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1977 while (varidx >= 0 && (nexttoken > lasttoken)) {
1978 anyduplicates |= thisduplicate;
1980 /* Extract the token number requested and set into the next variable context */
1981 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims);
1982 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1983 varidx + varoffset, wine_dbgstr_w(parm));
1984 if (varidx >=0) {
1985 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1986 varoffset++;
1987 if (((varidx + varoffset) %26) == 0) break;
1990 /* Find the next token */
1991 lasttoken = nexttoken;
1992 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1993 &starfound, &thisduplicate);
1996 /* If all the rest of the tokens were requested, and there is still space in
1997 the variable range, write them now */
1998 if (!anyduplicates && starfound && varidx >= 0 && ((varidx+varoffset) % 26)) {
1999 nexttoken++;
2000 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
2001 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2002 varidx + varoffset, wine_dbgstr_w(parm));
2003 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2006 /* Execute the body of the foor loop with these values */
2007 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
2008 CMD_LIST *thisCmdStart = cmdStart;
2009 *doExecuted = TRUE;
2010 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
2011 *cmdEnd = thisCmdStart;
2014 /* Free the duplicated strings, and restore the context */
2015 if (varidx >=0) {
2016 int i;
2017 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
2018 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
2019 (forloopcontext.variable[i] != nullW)) {
2020 heap_free(forloopcontext.variable[i]);
2025 /* Restore the original for variable contextx */
2026 forloopcontext = oldcontext;
2029 /**************************************************************************
2030 * WCMD_forf_getinputhandle
2032 * Return a file handle which can be used for reading the input lines,
2033 * either to a specific file (which may be quote delimited as we have to
2034 * read the parameters in raw mode) or to a command which we need to
2035 * execute. The command being executed runs in its own shell and stores
2036 * its data in a temporary file.
2038 * Parameters:
2039 * usebackq [I] - Indicates whether usebackq is in effect or not
2040 * itemStr [I] - The item to be handled, either a filename or
2041 * whole command string to execute
2042 * iscmd [I] - Identifies whether this is a command or not
2044 * Returns a file handle which can be used to read the input lines from.
2046 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2047 WCHAR temp_str[MAX_PATH];
2048 WCHAR temp_file[MAX_PATH];
2049 WCHAR temp_cmd[MAXSTRING];
2050 HANDLE hinput = INVALID_HANDLE_VALUE;
2051 static const WCHAR redirOutW[] = {'>','%','s','\0'};
2052 static const WCHAR cmdW[] = {'C','M','D','\0'};
2053 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
2054 '/','C',' ','"','%','s','"','\0'};
2056 /* Remove leading and trailing character */
2057 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2058 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2059 (!iscmd && (itemstr[0] == '"' && usebackq)))
2061 itemstr[strlenW(itemstr)-1] = 0x00;
2062 itemstr++;
2065 if (iscmd) {
2066 /* Get temp filename */
2067 GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
2068 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
2070 /* Redirect output to the temporary file */
2071 wsprintfW(temp_str, redirOutW, temp_file);
2072 wsprintfW(temp_cmd, cmdslashcW, itemstr);
2073 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2074 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2075 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2077 /* Open the file, read line by line and process */
2078 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2079 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2081 } else {
2082 /* Open the file, read line by line and process */
2083 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2084 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2085 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2087 return hinput;
2090 /**************************************************************************
2091 * WCMD_for
2093 * Batch file loop processing.
2095 * On entry: cmdList contains the syntax up to the set
2096 * next cmdList and all in that bracket contain the set data
2097 * next cmdlist contains the DO cmd
2098 * following that is either brackets or && entries (as per if)
2102 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2104 WIN32_FIND_DATAW fd;
2105 HANDLE hff;
2106 int i;
2107 static const WCHAR inW[] = {'i','n'};
2108 static const WCHAR doW[] = {'d','o'};
2109 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2110 WCHAR variable[4];
2111 int varidx = -1;
2112 WCHAR *oldvariablevalue;
2113 WCHAR *firstCmd;
2114 int thisDepth;
2115 WCHAR optionsRoot[MAX_PATH];
2116 DIRECTORY_STACK *dirsToWalk = NULL;
2117 BOOL expandDirs = FALSE;
2118 BOOL useNumbers = FALSE;
2119 BOOL doFileset = FALSE;
2120 BOOL doRecurse = FALSE;
2121 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2122 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2123 int itemNum;
2124 CMD_LIST *thisCmdStart;
2125 int parameterNo = 0;
2126 WCHAR forf_eol = 0;
2127 int forf_skip = 0;
2128 WCHAR forf_delims[256];
2129 WCHAR forf_tokens[MAXSTRING];
2130 BOOL forf_usebackq = FALSE;
2132 /* Handle optional qualifiers (multiple are allowed) */
2133 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2135 optionsRoot[0] = 0;
2136 while (thisArg && *thisArg == '/') {
2137 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2138 thisArg++;
2139 switch (toupperW(*thisArg)) {
2140 case 'D': expandDirs = TRUE; break;
2141 case 'L': useNumbers = TRUE; break;
2143 /* Recursive is special case - /R can have an optional path following it */
2144 /* filenamesets are another special case - /F can have an optional options following it */
2145 case 'R':
2146 case 'F':
2148 /* When recursing directories, use current directory as the starting point unless
2149 subsequently overridden */
2150 doRecurse = (toupperW(*thisArg) == 'R');
2151 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
2153 doFileset = (toupperW(*thisArg) == 'F');
2155 /* Retrieve next parameter to see if is root/options (raw form required
2156 with for /f, or unquoted in for /r) */
2157 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2159 /* Next parm is either qualifier, path/options or variable -
2160 only care about it if it is the path/options */
2161 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2162 parameterNo++;
2163 strcpyW(optionsRoot, thisArg);
2165 break;
2167 default:
2168 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2171 /* Step to next token */
2172 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2175 /* Ensure line continues with variable */
2176 if (*thisArg != '%') {
2177 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2178 return;
2181 /* With for /f parse the options if provided */
2182 if (doFileset) {
2183 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2184 forf_delims, forf_tokens, &forf_usebackq))
2186 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2187 return;
2190 /* Set up the list of directories to recurse if we are going to */
2191 } else if (doRecurse) {
2192 /* Allocate memory, add to list */
2193 dirsToWalk = heap_alloc(sizeof(DIRECTORY_STACK));
2194 dirsToWalk->next = NULL;
2195 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2196 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2199 /* Variable should follow */
2200 strcpyW(variable, thisArg);
2201 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2202 varidx = FOR_VAR_IDX(variable[1]);
2204 /* Ensure line continues with IN */
2205 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2206 if (!thisArg
2207 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2208 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
2209 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
2210 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2211 return;
2214 /* Save away where the set of data starts and the variable */
2215 thisDepth = (*cmdList)->bracketDepth;
2216 *cmdList = (*cmdList)->nextcommand;
2217 setStart = (*cmdList);
2219 /* Skip until the close bracket */
2220 WINE_TRACE("Searching %p as the set\n", *cmdList);
2221 while (*cmdList &&
2222 (*cmdList)->command != NULL &&
2223 (*cmdList)->bracketDepth > thisDepth) {
2224 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2225 *cmdList = (*cmdList)->nextcommand;
2228 /* Skip the close bracket, if there is one */
2229 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2231 /* Syntax error if missing close bracket, or nothing following it
2232 and once we have the complete set, we expect a DO */
2233 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2234 if ((*cmdList == NULL)
2235 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
2237 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2238 return;
2241 cmdEnd = *cmdList;
2243 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2244 mode, or once for the rest of the time. */
2245 do {
2247 /* Save away the starting position for the commands (and offset for the
2248 first one) */
2249 cmdStart = *cmdList;
2250 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2251 itemNum = 0;
2253 /* If we are recursing directories (ie /R), add all sub directories now, then
2254 prefix the root when searching for the item */
2255 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2257 thisSet = setStart;
2258 /* Loop through all set entries */
2259 while (thisSet &&
2260 thisSet->command != NULL &&
2261 thisSet->bracketDepth >= thisDepth) {
2263 /* Loop through all entries on the same line */
2264 WCHAR *item;
2265 WCHAR *itemStart;
2266 WCHAR buffer[MAXSTRING];
2268 WINE_TRACE("Processing for set %p\n", thisSet);
2269 i = 0;
2270 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2273 * If the parameter within the set has a wildcard then search for matching files
2274 * otherwise do a literal substitution.
2276 static const WCHAR wildcards[] = {'*','?','\0'};
2277 thisCmdStart = cmdStart;
2279 itemNum++;
2280 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2282 if (!useNumbers && !doFileset) {
2283 WCHAR fullitem[MAX_PATH];
2284 int prefixlen = 0;
2286 /* Now build the item to use / search for in the specified directory,
2287 as it is fully qualified in the /R case */
2288 if (dirsToWalk) {
2289 strcpyW(fullitem, dirsToWalk->dirName);
2290 strcatW(fullitem, slashW);
2291 strcatW(fullitem, item);
2292 } else {
2293 WCHAR *prefix = strrchrW(item, '\\');
2294 if (prefix) prefixlen = (prefix - item) + 1;
2295 strcpyW(fullitem, item);
2298 if (strpbrkW (fullitem, wildcards)) {
2299 hff = FindFirstFileW(fullitem, &fd);
2300 if (hff != INVALID_HANDLE_VALUE) {
2301 do {
2302 BOOL isDirectory = FALSE;
2304 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2306 /* Handle as files or dirs appropriately, but ignore . and .. */
2307 if (isDirectory == expandDirs &&
2308 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2309 (strcmpW(fd.cFileName, dotW) != 0))
2311 thisCmdStart = cmdStart;
2312 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2314 if (doRecurse) {
2315 strcpyW(fullitem, dirsToWalk->dirName);
2316 strcatW(fullitem, slashW);
2317 strcatW(fullitem, fd.cFileName);
2318 } else {
2319 if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
2320 fullitem[prefixlen] = 0x00;
2321 strcatW(fullitem, fd.cFileName);
2323 doExecuted = TRUE;
2325 /* Save away any existing for variable context (e.g. nested for loops)
2326 and restore it after executing the body of this for loop */
2327 if (varidx >= 0) {
2328 oldvariablevalue = forloopcontext.variable[varidx];
2329 forloopcontext.variable[varidx] = fullitem;
2331 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2332 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2334 cmdEnd = thisCmdStart;
2336 } while (FindNextFileW(hff, &fd) != 0);
2337 FindClose (hff);
2339 } else {
2340 doExecuted = TRUE;
2342 /* Save away any existing for variable context (e.g. nested for loops)
2343 and restore it after executing the body of this for loop */
2344 if (varidx >= 0) {
2345 oldvariablevalue = forloopcontext.variable[varidx];
2346 forloopcontext.variable[varidx] = fullitem;
2348 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2349 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2351 cmdEnd = thisCmdStart;
2354 } else if (useNumbers) {
2355 /* Convert the first 3 numbers to signed longs and save */
2356 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2357 /* else ignore them! */
2359 /* Filesets - either a list of files, or a command to run and parse the output */
2360 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2361 (forf_usebackq && *itemStart != '\''))) {
2363 HANDLE input;
2364 WCHAR *itemparm;
2366 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2367 wine_dbgstr_w(item));
2369 /* If backquote or single quote, we need to launch that command
2370 and parse the results - use a temporary file */
2371 if ((forf_usebackq && *itemStart == '`') ||
2372 (!forf_usebackq && *itemStart == '\'')) {
2374 /* Use itemstart because the command is the whole set, not just the first token */
2375 itemparm = itemStart;
2376 } else {
2378 /* Use item because the file to process is just the first item in the set */
2379 itemparm = item;
2381 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2383 /* Process the input file */
2384 if (input == INVALID_HANDLE_VALUE) {
2385 WCMD_print_error ();
2386 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2387 errorlevel = 1;
2388 return; /* FOR loop aborts at first failure here */
2390 } else {
2392 /* Read line by line until end of file */
2393 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2394 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2395 &forf_skip, forf_eol, forf_delims, forf_tokens);
2396 buffer[0] = 0;
2398 CloseHandle (input);
2401 /* When we have processed the item as a whole command, abort future set processing */
2402 if (itemparm==itemStart) {
2403 thisSet = NULL;
2404 break;
2407 /* Filesets - A string literal */
2408 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2409 (forf_usebackq && *itemStart == '\''))) {
2411 /* Remove leading and trailing character, ready to parse with delims= delimiters
2412 Note that the last quote is removed from the set and the string terminates
2413 there to mimic windows */
2414 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2415 if (strend) {
2416 *strend = 0x00;
2417 itemStart++;
2420 /* Copy the item away from the global buffer used by WCMD_parameter */
2421 strcpyW(buffer, itemStart);
2422 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2423 &forf_skip, forf_eol, forf_delims, forf_tokens);
2425 /* Only one string can be supplied in the whole set, abort future set processing */
2426 thisSet = NULL;
2427 break;
2430 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2431 i++;
2434 /* Move onto the next set line */
2435 if (thisSet) thisSet = thisSet->nextcommand;
2438 /* If /L is provided, now run the for loop */
2439 if (useNumbers) {
2440 WCHAR thisNum[20];
2441 static const WCHAR fmt[] = {'%','d','\0'};
2443 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2444 numbers[0], numbers[2], numbers[1]);
2445 for (i=numbers[0];
2446 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2447 i=i + numbers[1]) {
2449 sprintfW(thisNum, fmt, i);
2450 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2452 thisCmdStart = cmdStart;
2453 doExecuted = TRUE;
2455 /* Save away any existing for variable context (e.g. nested for loops)
2456 and restore it after executing the body of this for loop */
2457 if (varidx >= 0) {
2458 oldvariablevalue = forloopcontext.variable[varidx];
2459 forloopcontext.variable[varidx] = thisNum;
2461 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2462 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2464 cmdEnd = thisCmdStart;
2467 /* If we are walking directories, move on to any which remain */
2468 if (dirsToWalk != NULL) {
2469 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2470 heap_free(dirsToWalk->dirName);
2471 heap_free(dirsToWalk);
2472 dirsToWalk = nextDir;
2473 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2474 wine_dbgstr_w(dirsToWalk->dirName));
2475 else WINE_TRACE("Finished all directories.\n");
2478 } while (dirsToWalk != NULL);
2480 /* Now skip over the do part if we did not perform the for loop so far.
2481 We store in cmdEnd the next command after the do block, but we only
2482 know this if something was run. If it has not been, we need to calculate
2483 it. */
2484 if (!doExecuted) {
2485 thisCmdStart = cmdStart;
2486 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2487 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2488 cmdEnd = thisCmdStart;
2491 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2492 all processing, OR it should be pointing to the end of && processing OR
2493 it should be pointing at the NULL end of bracket for the DO. The return
2494 value needs to be the NEXT command to execute, which it either is, or
2495 we need to step over the closing bracket */
2496 *cmdList = cmdEnd;
2497 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2500 /**************************************************************************
2501 * WCMD_give_help
2503 * Simple on-line help. Help text is stored in the resource file.
2506 void WCMD_give_help (const WCHAR *args)
2508 size_t i;
2510 args = WCMD_skip_leading_spaces((WCHAR*) args);
2511 if (strlenW(args) == 0) {
2512 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2514 else {
2515 /* Display help message for builtin commands */
2516 for (i=0; i<=WCMD_EXIT; i++) {
2517 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2518 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2519 WCMD_output_asis (WCMD_LoadMessage(i));
2520 return;
2523 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2524 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2525 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2526 args, -1, externals[i], -1) == CSTR_EQUAL) {
2527 WCHAR cmd[128];
2528 static const WCHAR helpW[] = {' ', '/','?','\0'};
2529 strcpyW(cmd, args);
2530 strcatW(cmd, helpW);
2531 WCMD_run_program(cmd, FALSE);
2532 return;
2535 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2537 return;
2540 /****************************************************************************
2541 * WCMD_go_to
2543 * Batch file jump instruction. Not the most efficient algorithm ;-)
2544 * Prints error message if the specified label cannot be found - the file pointer is
2545 * then at EOF, effectively stopping the batch file.
2546 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2549 void WCMD_goto (CMD_LIST **cmdList) {
2551 WCHAR string[MAX_PATH];
2552 WCHAR *labelend = NULL;
2553 const WCHAR labelEndsW[] = {'>','<','|','&',' ',':','\t','\0'};
2555 /* Do not process any more parts of a processed multipart or multilines command */
2556 if (cmdList) *cmdList = NULL;
2558 if (context != NULL) {
2559 WCHAR *paramStart = param1, *str;
2560 static const WCHAR eofW[] = {':','e','o','f','\0'};
2562 if (param1[0] == 0x00) {
2563 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2564 return;
2567 /* Handle special :EOF label */
2568 if (lstrcmpiW (eofW, param1) == 0) {
2569 context -> skip_rest = TRUE;
2570 return;
2573 /* Support goto :label as well as goto label plus remove trailing chars */
2574 if (*paramStart == ':') paramStart++;
2575 labelend = strpbrkW(paramStart, labelEndsW);
2576 if (labelend) *labelend = 0x00;
2577 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
2579 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2580 while (*paramStart &&
2581 WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2582 str = string;
2584 /* Ignore leading whitespace or no-echo character */
2585 while (*str=='@' || isspaceW (*str)) str++;
2587 /* If the first real character is a : then this is a label */
2588 if (*str == ':') {
2589 str++;
2591 /* Skip spaces between : and label */
2592 while (isspaceW (*str)) str++;
2593 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
2595 /* Label ends at whitespace or redirection characters */
2596 labelend = strpbrkW(str, labelEndsW);
2597 if (labelend) *labelend = 0x00;
2598 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
2600 if (lstrcmpiW (str, paramStart) == 0) return;
2603 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2604 context -> skip_rest = TRUE;
2606 return;
2609 /*****************************************************************************
2610 * WCMD_pushd
2612 * Push a directory onto the stack
2615 void WCMD_pushd (const WCHAR *args)
2617 struct env_stack *curdir;
2618 WCHAR *thisdir;
2619 static const WCHAR parmD[] = {'/','D','\0'};
2621 if (strchrW(args, '/') != NULL) {
2622 SetLastError(ERROR_INVALID_PARAMETER);
2623 WCMD_print_error();
2624 return;
2627 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2628 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2629 if( !curdir || !thisdir ) {
2630 LocalFree(curdir);
2631 LocalFree(thisdir);
2632 WINE_ERR ("out of memory\n");
2633 return;
2636 /* Change directory using CD code with /D parameter */
2637 strcpyW(quals, parmD);
2638 GetCurrentDirectoryW (1024, thisdir);
2639 errorlevel = 0;
2640 WCMD_setshow_default(args);
2641 if (errorlevel) {
2642 LocalFree(curdir);
2643 LocalFree(thisdir);
2644 return;
2645 } else {
2646 curdir -> next = pushd_directories;
2647 curdir -> strings = thisdir;
2648 if (pushd_directories == NULL) {
2649 curdir -> u.stackdepth = 1;
2650 } else {
2651 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2653 pushd_directories = curdir;
2658 /*****************************************************************************
2659 * WCMD_popd
2661 * Pop a directory from the stack
2664 void WCMD_popd (void) {
2665 struct env_stack *temp = pushd_directories;
2667 if (!pushd_directories)
2668 return;
2670 /* pop the old environment from the stack, and make it the current dir */
2671 pushd_directories = temp->next;
2672 SetCurrentDirectoryW(temp->strings);
2673 LocalFree (temp->strings);
2674 LocalFree (temp);
2677 /*******************************************************************
2678 * evaluate_if_comparison
2680 * Evaluates an "if" comparison operation
2682 * PARAMS
2683 * leftOperand [I] left operand, non NULL
2684 * operator [I] "if" binary comparison operator, non NULL
2685 * rightOperand [I] right operand, non NULL
2686 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2688 * RETURNS
2689 * Success: 1 if operator applied to the operands evaluates to TRUE
2690 * 0 if operator applied to the operands evaluates to FALSE
2691 * Failure: -1 if operator is not recognized
2693 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2694 const WCHAR *rightOperand, int caseInsensitive)
2696 WCHAR *endptr_leftOp, *endptr_rightOp;
2697 long int leftOperand_int, rightOperand_int;
2698 BOOL int_operands;
2699 static const WCHAR lssW[] = {'l','s','s','\0'};
2700 static const WCHAR leqW[] = {'l','e','q','\0'};
2701 static const WCHAR equW[] = {'e','q','u','\0'};
2702 static const WCHAR neqW[] = {'n','e','q','\0'};
2703 static const WCHAR geqW[] = {'g','e','q','\0'};
2704 static const WCHAR gtrW[] = {'g','t','r','\0'};
2706 /* == is a special case, as it always compares strings */
2707 if (!lstrcmpiW(operator, eqeqW))
2708 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2709 : lstrcmpW (leftOperand, rightOperand) == 0;
2711 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2712 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2713 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2714 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2716 /* Perform actual (integer or string) comparison */
2717 if (!lstrcmpiW(operator, lssW)) {
2718 if (int_operands)
2719 return leftOperand_int < rightOperand_int;
2720 else
2721 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2722 : lstrcmpW (leftOperand, rightOperand) < 0;
2725 if (!lstrcmpiW(operator, leqW)) {
2726 if (int_operands)
2727 return leftOperand_int <= rightOperand_int;
2728 else
2729 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2730 : lstrcmpW (leftOperand, rightOperand) <= 0;
2733 if (!lstrcmpiW(operator, equW)) {
2734 if (int_operands)
2735 return leftOperand_int == rightOperand_int;
2736 else
2737 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2738 : lstrcmpW (leftOperand, rightOperand) == 0;
2741 if (!lstrcmpiW(operator, neqW)) {
2742 if (int_operands)
2743 return leftOperand_int != rightOperand_int;
2744 else
2745 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2746 : lstrcmpW (leftOperand, rightOperand) != 0;
2749 if (!lstrcmpiW(operator, geqW)) {
2750 if (int_operands)
2751 return leftOperand_int >= rightOperand_int;
2752 else
2753 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2754 : lstrcmpW (leftOperand, rightOperand) >= 0;
2757 if (!lstrcmpiW(operator, gtrW)) {
2758 if (int_operands)
2759 return leftOperand_int > rightOperand_int;
2760 else
2761 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2762 : lstrcmpW (leftOperand, rightOperand) > 0;
2765 return -1;
2768 /****************************************************************************
2769 * WCMD_if
2771 * Batch file conditional.
2773 * On entry, cmdlist will point to command containing the IF, and optionally
2774 * the first command to execute (if brackets not found)
2775 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2776 * If ('s were found, execute all within that bracket
2777 * Command may optionally be followed by an ELSE - need to skip instructions
2778 * in the else using the same logic
2780 * FIXME: Much more syntax checking needed!
2782 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2784 int negate; /* Negate condition */
2785 int test; /* Condition evaluation result */
2786 WCHAR condition[MAX_PATH], *command;
2787 static const WCHAR notW[] = {'n','o','t','\0'};
2788 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2789 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2790 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2791 static const WCHAR parmI[] = {'/','I','\0'};
2792 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2794 negate = !lstrcmpiW(param1,notW);
2795 strcpyW(condition, (negate ? param2 : param1));
2796 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2798 if (!lstrcmpiW (condition, errlvlW)) {
2799 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2800 WCHAR *endptr;
2801 long int param_int = strtolW(param, &endptr, 10);
2802 if (*endptr) goto syntax_err;
2803 test = ((long int)errorlevel >= param_int);
2804 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2806 else if (!lstrcmpiW (condition, existW)) {
2807 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2808 != INVALID_FILE_ATTRIBUTES);
2809 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2811 else if (!lstrcmpiW (condition, defdW)) {
2812 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2813 NULL, 0) > 0);
2814 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2816 else { /* comparison operation */
2817 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2818 WCHAR *paramStart;
2820 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, &paramStart, TRUE, FALSE));
2821 if (!*leftOperand)
2822 goto syntax_err;
2824 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2825 p = paramStart + strlenW(leftOperand);
2826 while (*p == ' ' || *p == '\t')
2827 p++;
2829 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2830 strcpyW(operator, eqeqW);
2831 else {
2832 strcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2833 if (!*operator) goto syntax_err;
2835 p += strlenW(operator);
2837 strcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2838 if (!*rightOperand)
2839 goto syntax_err;
2841 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2842 if (test == -1)
2843 goto syntax_err;
2845 p = paramStart + strlenW(rightOperand);
2846 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2849 /* Process rest of IF statement which is on the same line
2850 Note: This may process all or some of the cmdList (eg a GOTO) */
2851 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2852 return;
2854 syntax_err:
2855 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2858 /****************************************************************************
2859 * WCMD_move
2861 * Move a file, directory tree or wildcarded set of files.
2864 void WCMD_move (void)
2866 BOOL status;
2867 WIN32_FIND_DATAW fd;
2868 HANDLE hff;
2869 WCHAR input[MAX_PATH];
2870 WCHAR output[MAX_PATH];
2871 WCHAR drive[10];
2872 WCHAR dir[MAX_PATH];
2873 WCHAR fname[MAX_PATH];
2874 WCHAR ext[MAX_PATH];
2876 if (param1[0] == 0x00) {
2877 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2878 return;
2881 /* If no destination supplied, assume current directory */
2882 if (param2[0] == 0x00) {
2883 strcpyW(param2, dotW);
2886 /* If 2nd parm is directory, then use original filename */
2887 /* Convert partial path to full path */
2888 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2889 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2890 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2891 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2893 /* Split into components */
2894 WCMD_splitpath(input, drive, dir, fname, ext);
2896 hff = FindFirstFileW(input, &fd);
2897 if (hff == INVALID_HANDLE_VALUE)
2898 return;
2900 do {
2901 WCHAR dest[MAX_PATH];
2902 WCHAR src[MAX_PATH];
2903 DWORD attribs;
2904 BOOL ok = TRUE;
2906 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2908 /* Build src & dest name */
2909 strcpyW(src, drive);
2910 strcatW(src, dir);
2912 /* See if dest is an existing directory */
2913 attribs = GetFileAttributesW(output);
2914 if (attribs != INVALID_FILE_ATTRIBUTES &&
2915 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2916 strcpyW(dest, output);
2917 strcatW(dest, slashW);
2918 strcatW(dest, fd.cFileName);
2919 } else {
2920 strcpyW(dest, output);
2923 strcatW(src, fd.cFileName);
2925 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2926 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2928 /* If destination exists, prompt unless /Y supplied */
2929 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2930 BOOL force = FALSE;
2931 WCHAR copycmd[MAXSTRING];
2932 DWORD len;
2934 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2935 if (strstrW (quals, parmNoY))
2936 force = FALSE;
2937 else if (strstrW (quals, parmY))
2938 force = TRUE;
2939 else {
2940 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2941 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2942 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2943 && ! lstrcmpiW (copycmd, parmY));
2946 /* Prompt if overwriting */
2947 if (!force) {
2948 WCHAR* question;
2950 /* Ask for confirmation */
2951 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2952 ok = WCMD_ask_confirm(question, FALSE, NULL);
2953 LocalFree(question);
2955 /* So delete the destination prior to the move */
2956 if (ok) {
2957 if (!DeleteFileW(dest)) {
2958 WCMD_print_error ();
2959 errorlevel = 1;
2960 ok = FALSE;
2966 if (ok) {
2967 status = MoveFileW(src, dest);
2968 } else {
2969 status = TRUE;
2972 if (!status) {
2973 WCMD_print_error ();
2974 errorlevel = 1;
2976 } while (FindNextFileW(hff, &fd) != 0);
2978 FindClose(hff);
2981 /****************************************************************************
2982 * WCMD_pause
2984 * Suspend execution of a batch script until a key is typed
2987 void WCMD_pause (void)
2989 DWORD oldmode;
2990 BOOL have_console;
2991 DWORD count;
2992 WCHAR key;
2993 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2995 have_console = GetConsoleMode(hIn, &oldmode);
2996 if (have_console)
2997 SetConsoleMode(hIn, 0);
2999 WCMD_output_asis(anykey);
3000 WCMD_ReadFile(hIn, &key, 1, &count);
3001 if (have_console)
3002 SetConsoleMode(hIn, oldmode);
3005 /****************************************************************************
3006 * WCMD_remove_dir
3008 * Delete a directory.
3011 void WCMD_remove_dir (WCHAR *args) {
3013 int argno = 0;
3014 int argsProcessed = 0;
3015 WCHAR *argN = args;
3016 static const WCHAR parmS[] = {'/','S','\0'};
3017 static const WCHAR parmQ[] = {'/','Q','\0'};
3019 /* Loop through all args */
3020 while (argN) {
3021 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3022 if (argN && argN[0] != '/') {
3023 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
3024 wine_dbgstr_w(quals));
3025 argsProcessed++;
3027 /* If subdirectory search not supplied, just try to remove
3028 and report error if it fails (eg if it contains a file) */
3029 if (strstrW (quals, parmS) == NULL) {
3030 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
3032 /* Otherwise use ShFileOp to recursively remove a directory */
3033 } else {
3035 SHFILEOPSTRUCTW lpDir;
3037 /* Ask first */
3038 if (strstrW (quals, parmQ) == NULL) {
3039 BOOL ok;
3040 WCHAR question[MAXSTRING];
3041 static const WCHAR fmt[] = {'%','s',' ','\0'};
3043 /* Ask for confirmation */
3044 wsprintfW(question, fmt, thisArg);
3045 ok = WCMD_ask_confirm(question, TRUE, NULL);
3047 /* Abort if answer is 'N' */
3048 if (!ok) return;
3051 /* Do the delete */
3052 lpDir.hwnd = NULL;
3053 lpDir.pTo = NULL;
3054 lpDir.pFrom = thisArg;
3055 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
3056 lpDir.wFunc = FO_DELETE;
3058 /* SHFileOperationW needs file list with a double null termination */
3059 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3061 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3066 /* Handle no valid args */
3067 if (argsProcessed == 0) {
3068 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3069 return;
3074 /****************************************************************************
3075 * WCMD_rename
3077 * Rename a file.
3080 void WCMD_rename (void)
3082 BOOL status;
3083 HANDLE hff;
3084 WIN32_FIND_DATAW fd;
3085 WCHAR input[MAX_PATH];
3086 WCHAR *dotDst = NULL;
3087 WCHAR drive[10];
3088 WCHAR dir[MAX_PATH];
3089 WCHAR fname[MAX_PATH];
3090 WCHAR ext[MAX_PATH];
3092 errorlevel = 0;
3094 /* Must be at least two args */
3095 if (param1[0] == 0x00 || param2[0] == 0x00) {
3096 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3097 errorlevel = 1;
3098 return;
3101 /* Destination cannot contain a drive letter or directory separator */
3102 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
3103 SetLastError(ERROR_INVALID_PARAMETER);
3104 WCMD_print_error();
3105 errorlevel = 1;
3106 return;
3109 /* Convert partial path to full path */
3110 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
3111 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3112 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3113 dotDst = strchrW(param2, '.');
3115 /* Split into components */
3116 WCMD_splitpath(input, drive, dir, fname, ext);
3118 hff = FindFirstFileW(input, &fd);
3119 if (hff == INVALID_HANDLE_VALUE)
3120 return;
3122 do {
3123 WCHAR dest[MAX_PATH];
3124 WCHAR src[MAX_PATH];
3125 WCHAR *dotSrc = NULL;
3126 int dirLen;
3128 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3130 /* FIXME: If dest name or extension is *, replace with filename/ext
3131 part otherwise use supplied name. This supports:
3132 ren *.fred *.jim
3133 ren jim.* fred.* etc
3134 However, windows has a more complex algorithm supporting eg
3135 ?'s and *'s mid name */
3136 dotSrc = strchrW(fd.cFileName, '.');
3138 /* Build src & dest name */
3139 strcpyW(src, drive);
3140 strcatW(src, dir);
3141 strcpyW(dest, src);
3142 dirLen = strlenW(src);
3143 strcatW(src, fd.cFileName);
3145 /* Build name */
3146 if (param2[0] == '*') {
3147 strcatW(dest, fd.cFileName);
3148 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3149 } else {
3150 strcatW(dest, param2);
3151 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3154 /* Build Extension */
3155 if (dotDst && (*(dotDst+1)=='*')) {
3156 if (dotSrc) strcatW(dest, dotSrc);
3157 } else if (dotDst) {
3158 strcatW(dest, dotDst);
3161 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3162 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3164 status = MoveFileW(src, dest);
3166 if (!status) {
3167 WCMD_print_error ();
3168 errorlevel = 1;
3170 } while (FindNextFileW(hff, &fd) != 0);
3172 FindClose(hff);
3175 /*****************************************************************************
3176 * WCMD_dupenv
3178 * Make a copy of the environment.
3180 static WCHAR *WCMD_dupenv( const WCHAR *env )
3182 WCHAR *env_copy;
3183 int len;
3185 if( !env )
3186 return NULL;
3188 len = 0;
3189 while ( env[len] )
3190 len += (strlenW(&env[len]) + 1);
3192 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3193 if (!env_copy)
3195 WINE_ERR("out of memory\n");
3196 return env_copy;
3198 memcpy (env_copy, env, len*sizeof (WCHAR));
3199 env_copy[len] = 0;
3201 return env_copy;
3204 /*****************************************************************************
3205 * WCMD_setlocal
3207 * setlocal pushes the environment onto a stack
3208 * Save the environment as unicode so we don't screw anything up.
3210 void WCMD_setlocal (const WCHAR *s) {
3211 WCHAR *env;
3212 struct env_stack *env_copy;
3213 WCHAR cwd[MAX_PATH];
3214 BOOL newdelay;
3215 static const WCHAR ondelayW[] = {'E','N','A','B','L','E','D','E','L','A',
3216 'Y','E','D','E','X','P','A','N','S','I',
3217 'O','N','\0'};
3218 static const WCHAR offdelayW[] = {'D','I','S','A','B','L','E','D','E','L',
3219 'A','Y','E','D','E','X','P','A','N','S',
3220 'I','O','N','\0'};
3222 /* setlocal does nothing outside of batch programs */
3223 if (!context) return;
3225 /* DISABLEEXTENSIONS ignored */
3227 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3228 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3229 if (!strcmpiW(param1, ondelayW) || !strcmpiW(param2, ondelayW)) {
3230 newdelay = TRUE;
3231 } else if (!strcmpiW(param1, offdelayW) || !strcmpiW(param2, offdelayW)) {
3232 newdelay = FALSE;
3233 } else {
3234 newdelay = delayedsubst;
3236 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3238 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3239 if( !env_copy )
3241 WINE_ERR ("out of memory\n");
3242 return;
3245 env = GetEnvironmentStringsW ();
3246 env_copy->strings = WCMD_dupenv (env);
3247 if (env_copy->strings)
3249 env_copy->batchhandle = context->h;
3250 env_copy->next = saved_environment;
3251 env_copy->delayedsubst = delayedsubst;
3252 delayedsubst = newdelay;
3253 saved_environment = env_copy;
3255 /* Save the current drive letter */
3256 GetCurrentDirectoryW(MAX_PATH, cwd);
3257 env_copy->u.cwd = cwd[0];
3259 else
3260 LocalFree (env_copy);
3262 FreeEnvironmentStringsW (env);
3266 /*****************************************************************************
3267 * WCMD_endlocal
3269 * endlocal pops the environment off a stack
3270 * Note: When searching for '=', search from WCHAR position 1, to handle
3271 * special internal environment variables =C:, =D: etc
3273 void WCMD_endlocal (void) {
3274 WCHAR *env, *old, *p;
3275 struct env_stack *temp;
3276 int len, n;
3278 /* setlocal does nothing outside of batch programs */
3279 if (!context) return;
3281 /* setlocal needs a saved environment from within the same context (batch
3282 program) as it was saved in */
3283 if (!saved_environment || saved_environment->batchhandle != context->h)
3284 return;
3286 /* pop the old environment from the stack */
3287 temp = saved_environment;
3288 saved_environment = temp->next;
3290 /* delete the current environment, totally */
3291 env = GetEnvironmentStringsW ();
3292 old = WCMD_dupenv (env);
3293 len = 0;
3294 while (old[len]) {
3295 n = strlenW(&old[len]) + 1;
3296 p = strchrW(&old[len] + 1, '=');
3297 if (p)
3299 *p++ = 0;
3300 SetEnvironmentVariableW (&old[len], NULL);
3302 len += n;
3304 LocalFree (old);
3305 FreeEnvironmentStringsW (env);
3307 /* restore old environment */
3308 env = temp->strings;
3309 len = 0;
3310 delayedsubst = temp->delayedsubst;
3311 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3312 while (env[len]) {
3313 n = strlenW(&env[len]) + 1;
3314 p = strchrW(&env[len] + 1, '=');
3315 if (p)
3317 *p++ = 0;
3318 SetEnvironmentVariableW (&env[len], p);
3320 len += n;
3323 /* Restore current drive letter */
3324 if (IsCharAlphaW(temp->u.cwd)) {
3325 WCHAR envvar[4];
3326 WCHAR cwd[MAX_PATH];
3327 static const WCHAR fmt[] = {'=','%','c',':','\0'};
3329 wsprintfW(envvar, fmt, temp->u.cwd);
3330 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3331 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3332 SetCurrentDirectoryW(cwd);
3336 LocalFree (env);
3337 LocalFree (temp);
3340 /*****************************************************************************
3341 * WCMD_setshow_default
3343 * Set/Show the current default directory
3346 void WCMD_setshow_default (const WCHAR *args) {
3348 BOOL status;
3349 WCHAR string[1024];
3350 WCHAR cwd[1024];
3351 WCHAR *pos;
3352 WIN32_FIND_DATAW fd;
3353 HANDLE hff;
3354 static const WCHAR parmD[] = {'/','D','\0'};
3356 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3358 /* Skip /D and trailing whitespace if on the front of the command line */
3359 if (strlenW(args) >= 2 &&
3360 CompareStringW(LOCALE_USER_DEFAULT,
3361 NORM_IGNORECASE | SORT_STRINGSORT,
3362 args, 2, parmD, -1) == CSTR_EQUAL) {
3363 args += 2;
3364 while (*args && (*args==' ' || *args=='\t'))
3365 args++;
3368 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3369 if (strlenW(args) == 0) {
3370 strcatW (cwd, newlineW);
3371 WCMD_output_asis (cwd);
3373 else {
3374 /* Remove any double quotes, which may be in the
3375 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3376 pos = string;
3377 while (*args) {
3378 if (*args != '"') *pos++ = *args;
3379 args++;
3381 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3382 pos--;
3383 *pos = 0x00;
3385 /* Search for appropriate directory */
3386 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3387 hff = FindFirstFileW(string, &fd);
3388 if (hff != INVALID_HANDLE_VALUE) {
3389 do {
3390 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3391 WCHAR fpath[MAX_PATH];
3392 WCHAR drive[10];
3393 WCHAR dir[MAX_PATH];
3394 WCHAR fname[MAX_PATH];
3395 WCHAR ext[MAX_PATH];
3396 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3398 /* Convert path into actual directory spec */
3399 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3400 WCMD_splitpath(fpath, drive, dir, fname, ext);
3402 /* Rebuild path */
3403 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3404 break;
3406 } while (FindNextFileW(hff, &fd) != 0);
3407 FindClose(hff);
3410 /* Change to that directory */
3411 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3413 status = SetCurrentDirectoryW(string);
3414 if (!status) {
3415 errorlevel = 1;
3416 WCMD_print_error ();
3417 return;
3418 } else {
3420 /* Save away the actual new directory, to store as current location */
3421 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3423 /* Restore old directory if drive letter would change, and
3424 CD x:\directory /D (or pushd c:\directory) not supplied */
3425 if ((strstrW(quals, parmD) == NULL) &&
3426 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3427 SetCurrentDirectoryW(cwd);
3431 /* Set special =C: type environment variable, for drive letter of
3432 change of directory, even if path was restored due to missing
3433 /D (allows changing drive letter when not resident on that
3434 drive */
3435 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3436 WCHAR env[4];
3437 strcpyW(env, equalW);
3438 memcpy(env+1, string, 2 * sizeof(WCHAR));
3439 env[3] = 0x00;
3440 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3441 SetEnvironmentVariableW(env, string);
3445 return;
3448 /****************************************************************************
3449 * WCMD_setshow_date
3451 * Set/Show the system date
3452 * FIXME: Can't change date yet
3455 void WCMD_setshow_date (void) {
3457 WCHAR curdate[64], buffer[64];
3458 DWORD count;
3459 static const WCHAR parmT[] = {'/','T','\0'};
3461 if (strlenW(param1) == 0) {
3462 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3463 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3464 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3465 if (strstrW (quals, parmT) == NULL) {
3466 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3467 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3468 if (count > 2) {
3469 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3473 else WCMD_print_error ();
3475 else {
3476 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3480 /****************************************************************************
3481 * WCMD_compare
3482 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3483 * the equals sign.
3485 static int WCMD_compare( const void *a, const void *b )
3487 int r;
3488 const WCHAR * const *str_a = a, * const *str_b = b;
3489 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3490 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3491 if( r == CSTR_LESS_THAN ) return -1;
3492 if( r == CSTR_GREATER_THAN ) return 1;
3493 return 0;
3496 /****************************************************************************
3497 * WCMD_setshow_sortenv
3499 * sort variables into order for display
3500 * Optionally only display those who start with a stub
3501 * returns the count displayed
3503 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3505 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3506 const WCHAR **str;
3508 if (stub) stublen = strlenW(stub);
3510 /* count the number of strings, and the total length */
3511 while ( s[len] ) {
3512 len += (strlenW(&s[len]) + 1);
3513 count++;
3516 /* add the strings to an array */
3517 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3518 if( !str )
3519 return 0;
3520 str[0] = s;
3521 for( i=1; i<count; i++ )
3522 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3524 /* sort the array */
3525 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3527 /* print it */
3528 for( i=0; i<count; i++ ) {
3529 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3530 NORM_IGNORECASE | SORT_STRINGSORT,
3531 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3532 /* Don't display special internal variables */
3533 if (str[i][0] != '=') {
3534 WCMD_output_asis(str[i]);
3535 WCMD_output_asis(newlineW);
3536 displayedcount++;
3541 LocalFree( str );
3542 return displayedcount;
3545 /****************************************************************************
3546 * WCMD_getprecedence
3547 * Return the precedence of a particular operator
3549 static int WCMD_getprecedence(const WCHAR in)
3551 switch (in) {
3552 case '!':
3553 case '~':
3554 case OP_POSITIVE:
3555 case OP_NEGATIVE:
3556 return 8;
3557 case '*':
3558 case '/':
3559 case '%':
3560 return 7;
3561 case '+':
3562 case '-':
3563 return 6;
3564 case '<':
3565 case '>':
3566 return 5;
3567 case '&':
3568 return 4;
3569 case '^':
3570 return 3;
3571 case '|':
3572 return 2;
3573 case '=':
3574 case OP_ASSSIGNMUL:
3575 case OP_ASSSIGNDIV:
3576 case OP_ASSSIGNMOD:
3577 case OP_ASSSIGNADD:
3578 case OP_ASSSIGNSUB:
3579 case OP_ASSSIGNAND:
3580 case OP_ASSSIGNNOT:
3581 case OP_ASSSIGNOR:
3582 case OP_ASSSIGNSHL:
3583 case OP_ASSSIGNSHR:
3584 return 1;
3585 default:
3586 return 0;
3590 /****************************************************************************
3591 * WCMD_pushnumber
3592 * Push either a number or name (environment variable) onto the supplied
3593 * stack
3595 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3596 VARSTACK *thisstack = heap_alloc(sizeof(VARSTACK));
3597 thisstack->isnum = (var == NULL);
3598 if (var) {
3599 thisstack->variable = var;
3600 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3601 } else {
3602 thisstack->value = num;
3603 WINE_TRACE("Pushed number %d\n", num);
3605 thisstack->next = *varstack;
3606 *varstack = thisstack;
3609 /****************************************************************************
3610 * WCMD_peeknumber
3611 * Returns the value of the top number or environment variable on the stack
3612 * and leaves the item on the stack.
3614 static int WCMD_peeknumber(VARSTACK **varstack) {
3615 int result = 0;
3616 VARSTACK *thisvar;
3618 if (varstack) {
3619 thisvar = *varstack;
3620 if (!thisvar->isnum) {
3621 WCHAR tmpstr[MAXSTRING];
3622 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3623 result = strtoulW(tmpstr,NULL,0);
3625 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3626 } else {
3627 result = thisvar->value;
3630 WINE_TRACE("Peeked number %d\n", result);
3631 return result;
3634 /****************************************************************************
3635 * WCMD_popnumber
3636 * Returns the value of the top number or environment variable on the stack
3637 * and removes the item from the stack.
3639 static int WCMD_popnumber(VARSTACK **varstack) {
3640 int result = 0;
3641 VARSTACK *thisvar;
3643 if (varstack) {
3644 thisvar = *varstack;
3645 result = WCMD_peeknumber(varstack);
3646 if (!thisvar->isnum) heap_free(thisvar->variable);
3647 *varstack = thisvar->next;
3648 heap_free(thisvar);
3650 WINE_TRACE("Popped number %d\n", result);
3651 return result;
3654 /****************************************************************************
3655 * WCMD_pushoperator
3656 * Push an operator onto the supplied stack
3658 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3659 OPSTACK *thisstack = heap_alloc(sizeof(OPSTACK));
3660 thisstack->precedence = precedence;
3661 thisstack->op = op;
3662 thisstack->next = *opstack;
3663 WINE_TRACE("Pushed operator %c\n", op);
3664 *opstack = thisstack;
3667 /****************************************************************************
3668 * WCMD_popoperator
3669 * Returns the operator from the top of the stack and removes the item from
3670 * the stack.
3672 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3673 WCHAR result = 0;
3674 OPSTACK *thisop;
3676 if (opstack) {
3677 thisop = *opstack;
3678 result = thisop->op;
3679 *opstack = thisop->next;
3680 heap_free(thisop);
3682 WINE_TRACE("Popped operator %c\n", result);
3683 return result;
3686 /****************************************************************************
3687 * WCMD_reduce
3688 * Actions the top operator on the stack against the first and sometimes
3689 * second value on the variable stack, and pushes the result
3690 * Returns non-zero on error.
3692 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3693 WCHAR thisop;
3694 int var1,var2;
3695 int rc = 0;
3697 if (!*opstack || !*varstack) {
3698 WINE_TRACE("No operators for the reduce\n");
3699 return WCMD_NOOPERATOR;
3702 /* Remove the top operator */
3703 thisop = WCMD_popoperator(opstack);
3704 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
3706 /* One variable operators */
3707 var1 = WCMD_popnumber(varstack);
3708 switch (thisop) {
3709 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3710 break;
3711 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3712 break;
3713 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3714 break;
3715 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3716 break;
3719 /* Two variable operators */
3720 if (!*varstack) {
3721 WINE_TRACE("No operands left for the reduce?\n");
3722 return WCMD_NOOPERAND;
3724 switch (thisop) {
3725 case '!':
3726 case '~':
3727 case OP_POSITIVE:
3728 case OP_NEGATIVE:
3729 break; /* Handled above */
3730 case '*': var2 = WCMD_popnumber(varstack);
3731 WCMD_pushnumber(NULL, var2*var1, varstack);
3732 break;
3733 case '/': var2 = WCMD_popnumber(varstack);
3734 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3735 WCMD_pushnumber(NULL, var2/var1, varstack);
3736 break;
3737 case '+': var2 = WCMD_popnumber(varstack);
3738 WCMD_pushnumber(NULL, var2+var1, varstack);
3739 break;
3740 case '-': var2 = WCMD_popnumber(varstack);
3741 WCMD_pushnumber(NULL, var2-var1, varstack);
3742 break;
3743 case '&': var2 = WCMD_popnumber(varstack);
3744 WCMD_pushnumber(NULL, var2&var1, varstack);
3745 break;
3746 case '%': var2 = WCMD_popnumber(varstack);
3747 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3748 WCMD_pushnumber(NULL, var2%var1, varstack);
3749 break;
3750 case '^': var2 = WCMD_popnumber(varstack);
3751 WCMD_pushnumber(NULL, var2^var1, varstack);
3752 break;
3753 case '<': var2 = WCMD_popnumber(varstack);
3754 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3755 which differs from the compiler (for example gcc) so being explicit. */
3756 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3757 WCMD_pushnumber(NULL, 0, varstack);
3758 } else {
3759 WCMD_pushnumber(NULL, var2<<var1, varstack);
3761 break;
3762 case '>': var2 = WCMD_popnumber(varstack);
3763 WCMD_pushnumber(NULL, var2>>var1, varstack);
3764 break;
3765 case '|': var2 = WCMD_popnumber(varstack);
3766 WCMD_pushnumber(NULL, var2|var1, varstack);
3767 break;
3769 case OP_ASSSIGNMUL:
3770 case OP_ASSSIGNDIV:
3771 case OP_ASSSIGNMOD:
3772 case OP_ASSSIGNADD:
3773 case OP_ASSSIGNSUB:
3774 case OP_ASSSIGNAND:
3775 case OP_ASSSIGNNOT:
3776 case OP_ASSSIGNOR:
3777 case OP_ASSSIGNSHL:
3778 case OP_ASSSIGNSHR:
3780 int i = 0;
3782 /* The left of an equals must be one variable */
3783 if (!(*varstack) || (*varstack)->isnum) {
3784 return WCMD_NOOPERAND;
3787 /* Make the number stack grow by inserting the value of the variable */
3788 var2 = WCMD_peeknumber(varstack);
3789 WCMD_pushnumber(NULL, var2, varstack);
3790 WCMD_pushnumber(NULL, var1, varstack);
3792 /* Make the operand stack grow by pushing the assign operator plus the
3793 operator to perform */
3794 while (calcassignments[i].op != ' ' &&
3795 calcassignments[i].calculatedop != thisop) {
3796 i++;
3798 if (calcassignments[i].calculatedop == ' ') {
3799 WINE_ERR("Unexpected operator %c\n", thisop);
3800 return WCMD_NOOPERATOR;
3802 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3803 WCMD_pushoperator(calcassignments[i].op,
3804 WCMD_getprecedence(calcassignments[i].op), opstack);
3805 break;
3808 case '=':
3810 WCHAR intFormat[] = {'%','d','\0'};
3811 WCHAR result[MAXSTRING];
3813 /* Build the result, then push it onto the stack */
3814 sprintfW(result, intFormat, var1);
3815 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3816 wine_dbgstr_w(result));
3817 SetEnvironmentVariableW((*varstack)->variable, result);
3818 var2 = WCMD_popnumber(varstack);
3819 WCMD_pushnumber(NULL, var1, varstack);
3820 break;
3823 default: WINE_ERR("Unrecognized operator %c\n", thisop);
3826 return rc;
3830 /****************************************************************************
3831 * WCMD_handleExpression
3832 * Handles an expression provided to set /a - If it finds brackets, it uses
3833 * recursion to process the parts in brackets.
3835 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3837 static const WCHAR mathDelims[] = {' ','\t','(',')','!','~','-','*','/','%',
3838 '+','<','>','&','^','|','=',',','\0' };
3839 int rc = 0;
3840 WCHAR *pos;
3841 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3842 OPSTACK *opstackhead = NULL;
3843 VARSTACK *varstackhead = NULL;
3844 WCHAR foundhalf = 0;
3846 /* Initialize */
3847 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3848 pos = *expr;
3850 /* Iterate through until whole expression is processed */
3851 while (pos && *pos) {
3852 BOOL treatasnumber;
3854 /* Skip whitespace to get to the next character to process*/
3855 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3856 if (!*pos) goto exprreturn;
3858 /* If we have found anything other than an operator then it's a number/variable */
3859 if (strchrW(mathDelims, *pos) == NULL) {
3860 WCHAR *parmstart, *parm, *dupparm;
3861 WCHAR *nextpos;
3863 /* Cannot have an expression with var/number twice, without an operator
3864 in-between, nor or number following a half constructed << or >> operator */
3865 if (lastwasnumber || foundhalf) {
3866 rc = WCMD_NOOPERATOR;
3867 goto exprerrorreturn;
3869 lastwasnumber = TRUE;
3871 if (isdigitW(*pos)) {
3872 /* For a number - just push it onto the stack */
3873 int num = strtoulW(pos, &nextpos, 0);
3874 WCMD_pushnumber(NULL, num, &varstackhead);
3875 pos = nextpos;
3877 /* Verify the number was validly formed */
3878 if (*nextpos && (strchrW(mathDelims, *nextpos) == NULL)) {
3879 rc = WCMD_BADHEXOCT;
3880 goto exprerrorreturn;
3882 } else {
3884 /* For a variable - just push it onto the stack */
3885 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3886 dupparm = heap_strdupW(parm);
3887 WCMD_pushnumber(dupparm, 0, &varstackhead);
3888 pos = parmstart + strlenW(dupparm);
3890 continue;
3893 /* We have found an operator. Some operators are one character, some two, and the minus
3894 and plus signs need special processing as they can be either operators or just influence
3895 the parameter which follows them */
3896 if (foundhalf && (*pos != foundhalf)) {
3897 /* Badly constructed operator pair */
3898 rc = WCMD_NOOPERATOR;
3899 goto exprerrorreturn;
3902 treatasnumber = FALSE; /* We are processing an operand */
3903 switch (*pos) {
3905 /* > and < are special as they are double character operators (and spaces can be between them!)
3906 If we see these for the first time, set a flag, and second time around we continue.
3907 Note these double character operators are stored as just one of the characters on the stack */
3908 case '>':
3909 case '<': if (!foundhalf) {
3910 foundhalf = *pos;
3911 pos++;
3912 break;
3914 /* We have found the rest, so clear up the knowledge of the half completed part and
3915 drop through to normal operator processing */
3916 foundhalf = 0;
3917 /* drop through */
3919 case '=': if (*pos=='=') {
3920 /* = is special cased as if the last was an operator then we may have e.g. += or
3921 *= etc which we need to handle by replacing the operator that is on the stack
3922 with a calculated assignment equivalent */
3923 if (!lastwasnumber && opstackhead) {
3924 int i = 0;
3925 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3926 i++;
3928 if (calcassignments[i].op == ' ') {
3929 rc = WCMD_NOOPERAND;
3930 goto exprerrorreturn;
3931 } else {
3932 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3933 when the general operator handling happens further down. */
3934 *pos = calcassignments[i].calculatedop;
3935 WCMD_popoperator(&opstackhead);
3939 /* Drop though */
3941 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3942 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3943 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
3944 /* drop through */
3945 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
3946 /* drop through */
3948 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3949 case '!': /* drop through */
3950 case '~': /* drop through */
3951 case '/': /* drop through */
3952 case '%': /* drop through */
3953 case '&': /* drop through */
3954 case '^': /* drop through */
3955 case '*': /* drop through */
3956 case '|':
3957 /* General code for handling most of the operators - look at the
3958 precedence of the top item on the stack, and see if we need to
3959 action the stack before we push something else onto it. */
3961 int precedence = WCMD_getprecedence(*pos);
3962 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
3963 precedence, !opstackhead?-1:opstackhead->precedence);
3965 /* In general, for things with the same precedence, reduce immediately
3966 except for assignments and unary operators which do not */
3967 while (!rc && opstackhead &&
3968 ((opstackhead->precedence > precedence) ||
3969 ((opstackhead->precedence == precedence) &&
3970 (precedence != 1) && (precedence != 8)))) {
3971 rc = WCMD_reduce(&opstackhead, &varstackhead);
3973 if (rc) goto exprerrorreturn;
3974 WCMD_pushoperator(*pos, precedence, &opstackhead);
3975 pos++;
3976 break;
3979 /* comma means start a new expression, ie calculate what we have */
3980 case ',':
3982 int prevresult = -1;
3983 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
3984 while (!rc && opstackhead) {
3985 rc = WCMD_reduce(&opstackhead, &varstackhead);
3987 if (rc) goto exprerrorreturn;
3988 /* If we have anything other than one number left, error
3989 otherwise throw the number away */
3990 if (!varstackhead || varstackhead->next) {
3991 rc = WCMD_NOOPERATOR;
3992 goto exprerrorreturn;
3994 prevresult = WCMD_popnumber(&varstackhead);
3995 WINE_TRACE("Expression resolved to %d\n", prevresult);
3996 heap_free(varstackhead);
3997 varstackhead = NULL;
3998 pos++;
3999 break;
4002 /* Open bracket - use iteration to parse the inner expression, then continue */
4003 case '(' : {
4004 int exprresult = 0;
4005 pos++;
4006 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
4007 if (rc) goto exprerrorreturn;
4008 WCMD_pushnumber(NULL, exprresult, &varstackhead);
4009 break;
4012 /* Close bracket - we have finished this depth, calculate and return */
4013 case ')' : {
4014 pos++;
4015 treatasnumber = TRUE; /* Things in brackets result in a number */
4016 if (depth == 0) {
4017 rc = WCMD_BADPAREN;
4018 goto exprerrorreturn;
4020 goto exprreturn;
4023 default:
4024 WINE_ERR("Unrecognized operator %c\n", *pos);
4025 pos++;
4027 lastwasnumber = treatasnumber;
4030 exprreturn:
4031 *expr = pos;
4033 /* We need to reduce until we have a single number (or variable) on the
4034 stack and set the return value to that */
4035 while (!rc && opstackhead) {
4036 rc = WCMD_reduce(&opstackhead, &varstackhead);
4038 if (rc) goto exprerrorreturn;
4040 /* If we have anything other than one number left, error
4041 otherwise throw the number away */
4042 if (!varstackhead || varstackhead->next) {
4043 rc = WCMD_NOOPERATOR;
4044 goto exprerrorreturn;
4047 /* Now get the number (and convert if it's just a variable name) */
4048 *ret = WCMD_popnumber(&varstackhead);
4050 exprerrorreturn:
4051 /* Free all remaining memory */
4052 while (opstackhead) WCMD_popoperator(&opstackhead);
4053 while (varstackhead) WCMD_popnumber(&varstackhead);
4055 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
4056 return rc;
4059 /****************************************************************************
4060 * WCMD_setshow_env
4062 * Set/Show the environment variables
4065 void WCMD_setshow_env (WCHAR *s) {
4067 LPVOID env;
4068 WCHAR *p;
4069 BOOL status;
4070 static const WCHAR parmP[] = {'/','P','\0'};
4071 static const WCHAR parmA[] = {'/','A','\0'};
4072 WCHAR string[MAXSTRING];
4074 if (param1[0] == 0x00 && quals[0] == 0x00) {
4075 env = GetEnvironmentStringsW();
4076 WCMD_setshow_sortenv( env, NULL );
4077 return;
4080 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4081 if (CompareStringW(LOCALE_USER_DEFAULT,
4082 NORM_IGNORECASE | SORT_STRINGSORT,
4083 s, 2, parmP, -1) == CSTR_EQUAL) {
4084 DWORD count;
4086 s += 2;
4087 while (*s && (*s==' ' || *s=='\t')) s++;
4088 /* set /P "var=value"jim ignores anything after the last quote */
4089 if (*s=='\"') {
4090 WCHAR *lastquote;
4091 lastquote = WCMD_strip_quotes(s);
4092 if (lastquote) *lastquote = 0x00;
4093 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4096 /* If no parameter, or no '=' sign, return an error */
4097 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
4098 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4099 return;
4102 /* Output the prompt */
4103 *p++ = '\0';
4104 if (strlenW(p) != 0) WCMD_output_asis(p);
4106 /* Read the reply */
4107 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4108 if (count > 1) {
4109 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4110 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4111 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4112 wine_dbgstr_w(string));
4113 SetEnvironmentVariableW(s, string);
4116 /* See if /A supplied, and if so calculate the results of all the expressions */
4117 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4118 NORM_IGNORECASE | SORT_STRINGSORT,
4119 s, 2, parmA, -1) == CSTR_EQUAL) {
4120 /* /A supplied, so evaluate expressions and set variables appropriately */
4121 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4122 /* of the final computation */
4123 int result = 0;
4124 int rc = 0;
4125 WCHAR *thisexpr;
4126 WCHAR *src,*dst;
4128 /* Remove all quotes before doing any calculations */
4129 thisexpr = heap_alloc((strlenW(s+2)+1) * sizeof(WCHAR));
4130 src = s+2;
4131 dst = thisexpr;
4132 while (*src) {
4133 if (*src != '"') *dst++ = *src;
4134 src++;
4136 *dst = 0;
4138 /* Now calculate the results of the expression */
4139 src = thisexpr;
4140 rc = WCMD_handleExpression(&src, &result, 0);
4141 heap_free(thisexpr);
4143 /* If parsing failed, issue the error message */
4144 if (rc > 0) {
4145 WCMD_output_stderr(WCMD_LoadMessage(rc));
4146 return;
4149 /* If we have no context (interactive or cmd.exe /c) print the final result */
4150 if (!context) {
4151 static const WCHAR fmt[] = {'%','d','\0'};
4152 sprintfW(string, fmt, result);
4153 WCMD_output_asis(string);
4156 } else {
4157 DWORD gle;
4159 /* set "var=value"jim ignores anything after the last quote */
4160 if (*s=='\"') {
4161 WCHAR *lastquote;
4162 lastquote = WCMD_strip_quotes(s);
4163 if (lastquote) *lastquote = 0x00;
4164 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4167 p = strchrW (s, '=');
4168 if (p == NULL) {
4169 env = GetEnvironmentStringsW();
4170 if (WCMD_setshow_sortenv( env, s ) == 0) {
4171 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4172 errorlevel = 1;
4174 return;
4176 *p++ = '\0';
4178 if (strlenW(p) == 0) p = NULL;
4179 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4180 wine_dbgstr_w(p));
4181 status = SetEnvironmentVariableW(s, p);
4182 gle = GetLastError();
4183 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4184 errorlevel = 1;
4185 } else if (!status) WCMD_print_error();
4186 else errorlevel = 0;
4190 /****************************************************************************
4191 * WCMD_setshow_path
4193 * Set/Show the path environment variable
4196 void WCMD_setshow_path (const WCHAR *args) {
4198 WCHAR string[1024];
4199 DWORD status;
4200 static const WCHAR pathW[] = {'P','A','T','H','\0'};
4201 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
4203 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
4204 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
4205 if (status != 0) {
4206 WCMD_output_asis ( pathEqW);
4207 WCMD_output_asis ( string);
4208 WCMD_output_asis ( newlineW);
4210 else {
4211 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4214 else {
4215 if (*args == '=') args++; /* Skip leading '=' */
4216 status = SetEnvironmentVariableW(pathW, args);
4217 if (!status) WCMD_print_error();
4221 /****************************************************************************
4222 * WCMD_setshow_prompt
4224 * Set or show the command prompt.
4227 void WCMD_setshow_prompt (void) {
4229 WCHAR *s;
4230 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
4232 if (strlenW(param1) == 0) {
4233 SetEnvironmentVariableW(promptW, NULL);
4235 else {
4236 s = param1;
4237 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4238 if (strlenW(s) == 0) {
4239 SetEnvironmentVariableW(promptW, NULL);
4241 else SetEnvironmentVariableW(promptW, s);
4245 /****************************************************************************
4246 * WCMD_setshow_time
4248 * Set/Show the system time
4249 * FIXME: Can't change time yet
4252 void WCMD_setshow_time (void) {
4254 WCHAR curtime[64], buffer[64];
4255 DWORD count;
4256 SYSTEMTIME st;
4257 static const WCHAR parmT[] = {'/','T','\0'};
4259 if (strlenW(param1) == 0) {
4260 GetLocalTime(&st);
4261 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
4262 curtime, sizeof(curtime)/sizeof(WCHAR))) {
4263 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4264 if (strstrW (quals, parmT) == NULL) {
4265 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4266 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4267 if (count > 2) {
4268 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4272 else WCMD_print_error ();
4274 else {
4275 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4279 /****************************************************************************
4280 * WCMD_shift
4282 * Shift batch parameters.
4283 * Optional /n says where to start shifting (n=0-8)
4286 void WCMD_shift (const WCHAR *args) {
4287 int start;
4289 if (context != NULL) {
4290 WCHAR *pos = strchrW(args, '/');
4291 int i;
4293 if (pos == NULL) {
4294 start = 0;
4295 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4296 start = (*(pos+1) - '0');
4297 } else {
4298 SetLastError(ERROR_INVALID_PARAMETER);
4299 WCMD_print_error();
4300 return;
4303 WINE_TRACE("Shifting variables, starting at %d\n", start);
4304 for (i=start;i<=8;i++) {
4305 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4307 context -> shift_count[9] = context -> shift_count[9] + 1;
4312 /****************************************************************************
4313 * WCMD_start
4315 void WCMD_start(WCHAR *args)
4317 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
4318 '\\','s','t','a','r','t','.','e','x','e',0};
4319 static const WCHAR startDelims[] = { ' ', '\t', '/', '\0' };
4320 static const WCHAR prefixQuote[] = {'"','\\','"','\0'};
4321 static const WCHAR postfixQuote[] = {'\\','"','"','\0'};
4322 int argno;
4323 int have_title;
4324 WCHAR file[MAX_PATH];
4325 WCHAR *cmdline;
4326 STARTUPINFOW st;
4327 PROCESS_INFORMATION pi;
4329 GetWindowsDirectoryW( file, MAX_PATH );
4330 strcatW( file, exeW );
4331 cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 8) * sizeof(WCHAR) );
4332 strcpyW( cmdline, file );
4333 strcatW( cmdline, spaceW );
4335 /* The start built-in has some special command-line parsing properties
4336 * which will be outlined here.
4338 * both '\t' and ' ' are argument separators
4339 * '/' has a special double role as both separator and switch prefix, e.g.
4341 * > start /low/i
4342 * or
4343 * > start "title"/i
4345 * are valid ways to pass multiple options to start. In the latter case
4346 * '/i' is not a part of the title but parsed as a switch.
4348 * However, '=', ';' and ',' are not separators:
4349 * > start "deus"=ex,machina
4351 * will in fact open a console titled 'deus=ex,machina'
4353 * The title argument parsing code is only interested in quotes themselves,
4354 * it does not respect escaping of any kind and all quotes are dropped
4355 * from the resulting title, therefore:
4357 * > start "\"" hello"/low
4359 * actually opens a console titled '\ hello' with low priorities.
4361 * To not break compatibility with wine programs relying on
4362 * wine's separate 'start.exe', this program's peculiar console
4363 * title parsing is actually implemented in 'cmd.exe' which is the
4364 * application native Windows programs will use to invoke 'start'.
4366 * WCMD_parameter_with_delims will take care of everything for us.
4368 have_title = FALSE;
4369 for (argno=0; ; argno++) {
4370 WCHAR *thisArg, *argN;
4372 argN = NULL;
4373 thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, startDelims);
4375 /* No more parameters */
4376 if (!argN)
4377 break;
4379 /* Found the title */
4380 if (argN[0] == '"') {
4381 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
4382 have_title = TRUE;
4384 /* Copy all of the cmdline processed */
4385 memcpy(cmdline, args, sizeof(WCHAR) * (argN - args));
4386 cmdline[argN - args] = '\0';
4388 /* Add quoted title */
4389 strcatW(cmdline, prefixQuote);
4390 strcatW(cmdline, thisArg);
4391 strcatW(cmdline, postfixQuote);
4393 /* Concatenate remaining command-line */
4394 thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, startDelims);
4395 strcatW(cmdline, argN + strlenW(thisArg));
4397 break;
4400 /* Skipping a regular argument? */
4401 else if (argN != args && argN[-1] == '/') {
4402 continue;
4404 /* Not an argument nor the title, start of program arguments,
4405 * stop looking for title.
4407 } else
4408 break;
4411 /* build command-line if not built yet */
4412 if (!have_title) {
4413 strcatW( cmdline, args );
4416 memset( &st, 0, sizeof(STARTUPINFOW) );
4417 st.cb = sizeof(STARTUPINFOW);
4419 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4421 WaitForSingleObject( pi.hProcess, INFINITE );
4422 GetExitCodeProcess( pi.hProcess, &errorlevel );
4423 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4424 CloseHandle(pi.hProcess);
4425 CloseHandle(pi.hThread);
4427 else
4429 SetLastError(ERROR_FILE_NOT_FOUND);
4430 WCMD_print_error ();
4431 errorlevel = 9009;
4433 heap_free(cmdline);
4436 /****************************************************************************
4437 * WCMD_title
4439 * Set the console title
4441 void WCMD_title (const WCHAR *args) {
4442 SetConsoleTitleW(args);
4445 /****************************************************************************
4446 * WCMD_type
4448 * Copy a file to standard output.
4451 void WCMD_type (WCHAR *args) {
4453 int argno = 0;
4454 WCHAR *argN = args;
4455 BOOL writeHeaders = FALSE;
4457 if (param1[0] == 0x00) {
4458 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4459 return;
4462 if (param2[0] != 0x00) writeHeaders = TRUE;
4464 /* Loop through all args */
4465 errorlevel = 0;
4466 while (argN) {
4467 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4469 HANDLE h;
4470 WCHAR buffer[512];
4471 DWORD count;
4473 if (!argN) break;
4475 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4476 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4477 FILE_ATTRIBUTE_NORMAL, NULL);
4478 if (h == INVALID_HANDLE_VALUE) {
4479 WCMD_print_error ();
4480 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4481 errorlevel = 1;
4482 } else {
4483 if (writeHeaders) {
4484 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
4485 WCMD_output(fmt, thisArg);
4487 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
4488 if (count == 0) break; /* ReadFile reports success on EOF! */
4489 buffer[count] = 0;
4490 WCMD_output_asis (buffer);
4492 CloseHandle (h);
4497 /****************************************************************************
4498 * WCMD_more
4500 * Output either a file or stdin to screen in pages
4503 void WCMD_more (WCHAR *args) {
4505 int argno = 0;
4506 WCHAR *argN = args;
4507 WCHAR moreStr[100];
4508 WCHAR moreStrPage[100];
4509 WCHAR buffer[512];
4510 DWORD count;
4511 static const WCHAR moreStart[] = {'-','-',' ','\0'};
4512 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
4513 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
4514 ')',' ','-','-','\n','\0'};
4515 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
4517 /* Prefix the NLS more with '-- ', then load the text */
4518 errorlevel = 0;
4519 strcpyW(moreStr, moreStart);
4520 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
4521 (sizeof(moreStr)/sizeof(WCHAR))-3);
4523 if (param1[0] == 0x00) {
4525 /* Wine implements pipes via temporary files, and hence stdin is
4526 effectively reading from the file. This means the prompts for
4527 more are satisfied by the next line from the input (file). To
4528 avoid this, ensure stdin is to the console */
4529 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4530 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
4531 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4532 FILE_ATTRIBUTE_NORMAL, 0);
4533 WINE_TRACE("No parms - working probably in pipe mode\n");
4534 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4536 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4537 once you get in this bit unless due to a pipe, it's going to end badly... */
4538 wsprintfW(moreStrPage, moreFmt, moreStr);
4540 WCMD_enter_paged_mode(moreStrPage);
4541 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4542 if (count == 0) break; /* ReadFile reports success on EOF! */
4543 buffer[count] = 0;
4544 WCMD_output_asis (buffer);
4546 WCMD_leave_paged_mode();
4548 /* Restore stdin to what it was */
4549 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4550 CloseHandle(hConIn);
4552 return;
4553 } else {
4554 BOOL needsPause = FALSE;
4556 /* Loop through all args */
4557 WINE_TRACE("Parms supplied - working through each file\n");
4558 WCMD_enter_paged_mode(moreStrPage);
4560 while (argN) {
4561 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4562 HANDLE h;
4564 if (!argN) break;
4566 if (needsPause) {
4568 /* Wait */
4569 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
4570 WCMD_leave_paged_mode();
4571 WCMD_output_asis(moreStrPage);
4572 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4573 WCMD_enter_paged_mode(moreStrPage);
4577 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4578 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4579 FILE_ATTRIBUTE_NORMAL, NULL);
4580 if (h == INVALID_HANDLE_VALUE) {
4581 WCMD_print_error ();
4582 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4583 errorlevel = 1;
4584 } else {
4585 ULONG64 curPos = 0;
4586 ULONG64 fileLen = 0;
4587 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4589 /* Get the file size */
4590 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4591 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4593 needsPause = TRUE;
4594 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4595 if (count == 0) break; /* ReadFile reports success on EOF! */
4596 buffer[count] = 0;
4597 curPos += count;
4599 /* Update % count (would be used in WCMD_output_asis as prompt) */
4600 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
4602 WCMD_output_asis (buffer);
4604 CloseHandle (h);
4608 WCMD_leave_paged_mode();
4612 /****************************************************************************
4613 * WCMD_verify
4615 * Display verify flag.
4616 * FIXME: We don't actually do anything with the verify flag other than toggle
4617 * it...
4620 void WCMD_verify (const WCHAR *args) {
4622 int count;
4624 count = strlenW(args);
4625 if (count == 0) {
4626 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
4627 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
4628 return;
4630 if (lstrcmpiW(args, onW) == 0) {
4631 verify_mode = TRUE;
4632 return;
4634 else if (lstrcmpiW(args, offW) == 0) {
4635 verify_mode = FALSE;
4636 return;
4638 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4641 /****************************************************************************
4642 * WCMD_version
4644 * Display version info.
4647 void WCMD_version (void) {
4649 WCMD_output_asis (version_string);
4653 /****************************************************************************
4654 * WCMD_volume
4656 * Display volume information (set_label = FALSE)
4657 * Additionally set volume label (set_label = TRUE)
4658 * Returns 1 on success, 0 otherwise
4661 int WCMD_volume(BOOL set_label, const WCHAR *path)
4663 DWORD count, serial;
4664 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4665 BOOL status;
4667 if (strlenW(path) == 0) {
4668 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
4669 if (!status) {
4670 WCMD_print_error ();
4671 return 0;
4673 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
4674 &serial, NULL, NULL, NULL, 0);
4676 else {
4677 static const WCHAR fmt[] = {'%','s','\\','\0'};
4678 if ((path[1] != ':') || (strlenW(path) != 2)) {
4679 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4680 return 0;
4682 wsprintfW (curdir, fmt, path);
4683 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
4684 &serial, NULL,
4685 NULL, NULL, 0);
4687 if (!status) {
4688 WCMD_print_error ();
4689 return 0;
4691 if (label[0] != '\0') {
4692 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4693 curdir[0], label);
4695 else {
4696 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4697 curdir[0]);
4699 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4700 HIWORD(serial), LOWORD(serial));
4701 if (set_label) {
4702 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4703 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4704 if (count > 1) {
4705 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4706 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4708 if (strlenW(path) != 0) {
4709 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4711 else {
4712 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4715 return 1;
4718 /**************************************************************************
4719 * WCMD_exit
4721 * Exit either the process, or just this batch program
4725 void WCMD_exit (CMD_LIST **cmdList) {
4727 static const WCHAR parmB[] = {'/','B','\0'};
4728 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
4730 if (context && lstrcmpiW(quals, parmB) == 0) {
4731 errorlevel = rc;
4732 context -> skip_rest = TRUE;
4733 *cmdList = NULL;
4734 } else {
4735 ExitProcess(rc);
4740 /*****************************************************************************
4741 * WCMD_assoc
4743 * Lists or sets file associations (assoc = TRUE)
4744 * Lists or sets file types (assoc = FALSE)
4746 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4748 HKEY key;
4749 DWORD accessOptions = KEY_READ;
4750 WCHAR *newValue;
4751 LONG rc = ERROR_SUCCESS;
4752 WCHAR keyValue[MAXSTRING];
4753 DWORD valueLen = MAXSTRING;
4754 HKEY readKey;
4755 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
4756 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4758 /* See if parameter includes '=' */
4759 errorlevel = 0;
4760 newValue = strchrW(args, '=');
4761 if (newValue) accessOptions |= KEY_WRITE;
4763 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4764 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
4765 accessOptions, &key) != ERROR_SUCCESS) {
4766 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4767 return;
4770 /* If no parameters then list all associations */
4771 if (*args == 0x00) {
4772 int index = 0;
4774 /* Enumerate all the keys */
4775 while (rc != ERROR_NO_MORE_ITEMS) {
4776 WCHAR keyName[MAXSTRING];
4777 DWORD nameLen;
4779 /* Find the next value */
4780 nameLen = MAXSTRING;
4781 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4783 if (rc == ERROR_SUCCESS) {
4785 /* Only interested in extension ones if assoc, or others
4786 if not assoc */
4787 if ((keyName[0] == '.' && assoc) ||
4788 (!(keyName[0] == '.') && (!assoc)))
4790 WCHAR subkey[MAXSTRING];
4791 strcpyW(subkey, keyName);
4792 if (!assoc) strcatW(subkey, shOpCmdW);
4794 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4796 valueLen = sizeof(keyValue)/sizeof(WCHAR);
4797 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4798 WCMD_output_asis(keyName);
4799 WCMD_output_asis(equalW);
4800 /* If no default value found, leave line empty after '=' */
4801 if (rc == ERROR_SUCCESS) {
4802 WCMD_output_asis(keyValue);
4804 WCMD_output_asis(newlineW);
4805 RegCloseKey(readKey);
4811 } else {
4813 /* Parameter supplied - if no '=' on command line, it's a query */
4814 if (newValue == NULL) {
4815 WCHAR *space;
4816 WCHAR subkey[MAXSTRING];
4818 /* Query terminates the parameter at the first space */
4819 strcpyW(keyValue, args);
4820 space = strchrW(keyValue, ' ');
4821 if (space) *space=0x00;
4823 /* Set up key name */
4824 strcpyW(subkey, keyValue);
4825 if (!assoc) strcatW(subkey, shOpCmdW);
4827 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4829 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4830 WCMD_output_asis(args);
4831 WCMD_output_asis(equalW);
4832 /* If no default value found, leave line empty after '=' */
4833 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4834 WCMD_output_asis(newlineW);
4835 RegCloseKey(readKey);
4837 } else {
4838 WCHAR msgbuffer[MAXSTRING];
4840 /* Load the translated 'File association not found' */
4841 if (assoc) {
4842 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4843 } else {
4844 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4846 WCMD_output_stderr(msgbuffer, keyValue);
4847 errorlevel = 2;
4850 /* Not a query - it's a set or clear of a value */
4851 } else {
4853 WCHAR subkey[MAXSTRING];
4855 /* Get pointer to new value */
4856 *newValue = 0x00;
4857 newValue++;
4859 /* Set up key name */
4860 strcpyW(subkey, args);
4861 if (!assoc) strcatW(subkey, shOpCmdW);
4863 /* If nothing after '=' then clear value - only valid for ASSOC */
4864 if (*newValue == 0x00) {
4866 if (assoc) rc = RegDeleteKeyW(key, args);
4867 if (assoc && rc == ERROR_SUCCESS) {
4868 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4870 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4871 WCMD_print_error();
4872 errorlevel = 2;
4874 } else {
4875 WCHAR msgbuffer[MAXSTRING];
4877 /* Load the translated 'File association not found' */
4878 if (assoc) {
4879 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
4880 sizeof(msgbuffer)/sizeof(WCHAR));
4881 } else {
4882 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
4883 sizeof(msgbuffer)/sizeof(WCHAR));
4885 WCMD_output_stderr(msgbuffer, keyValue);
4886 errorlevel = 2;
4889 /* It really is a set value = contents */
4890 } else {
4891 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4892 accessOptions, NULL, &readKey, NULL);
4893 if (rc == ERROR_SUCCESS) {
4894 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4895 (LPBYTE)newValue,
4896 sizeof(WCHAR) * (strlenW(newValue) + 1));
4897 RegCloseKey(readKey);
4900 if (rc != ERROR_SUCCESS) {
4901 WCMD_print_error();
4902 errorlevel = 2;
4903 } else {
4904 WCMD_output_asis(args);
4905 WCMD_output_asis(equalW);
4906 WCMD_output_asis(newValue);
4907 WCMD_output_asis(newlineW);
4913 /* Clean up */
4914 RegCloseKey(key);
4917 /****************************************************************************
4918 * WCMD_color
4920 * Colors the terminal screen.
4923 void WCMD_color (void) {
4925 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4926 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4928 if (param1[0] != 0x00 && strlenW(param1) > 2) {
4929 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4930 return;
4933 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4935 COORD topLeft;
4936 DWORD screenSize;
4937 DWORD color = 0;
4939 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4941 topLeft.X = 0;
4942 topLeft.Y = 0;
4944 /* Convert the color hex digits */
4945 if (param1[0] == 0x00) {
4946 color = defaultColor;
4947 } else {
4948 color = strtoulW(param1, NULL, 16);
4951 /* Fail if fg == bg color */
4952 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4953 errorlevel = 1;
4954 return;
4957 /* Set the current screen contents and ensure all future writes
4958 remain this color */
4959 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4960 SetConsoleTextAttribute(hStdOut, color);
4964 /****************************************************************************
4965 * WCMD_mklink
4968 void WCMD_mklink(WCHAR *args)
4970 int argno = 0;
4971 WCHAR *argN = args;
4972 BOOL isdir = FALSE;
4973 BOOL junction = FALSE;
4974 BOOL hard = FALSE;
4975 BOOL ret = FALSE;
4976 WCHAR file1[MAX_PATH];
4977 WCHAR file2[MAX_PATH];
4978 static const WCHAR optD[] = {'/', 'D', '\0'};
4979 static const WCHAR optH[] = {'/', 'H', '\0'};
4980 static const WCHAR optJ[] = {'/', 'J', '\0'};
4982 if (param1[0] == 0x00 || param2[0] == 0x00) {
4983 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4984 return;
4987 file1[0] = 0;
4989 while (argN) {
4990 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4992 if (!argN) break;
4994 WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4996 if(lstrcmpiW(thisArg, optD) == 0)
4997 isdir = TRUE;
4998 else if(lstrcmpiW(thisArg, optH) == 0)
4999 hard = TRUE;
5000 else if(lstrcmpiW(thisArg, optJ) == 0)
5001 junction = TRUE;
5002 else {
5003 if(!file1[0])
5004 lstrcpyW(file1, thisArg);
5005 else
5006 lstrcpyW(file2, thisArg);
5010 if(hard)
5011 ret = CreateHardLinkW(file1, file2, NULL);
5012 else if(!junction)
5013 ret = CreateSymbolicLinkW(file1, file2, isdir);
5014 else
5015 WINE_TRACE("Juction links currently not supported.\n");
5017 if(!ret)
5018 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), file1);