dsound: mix float natively
[wine/multimedia.git] / programs / cmd / builtins.c
bloba444330d80c95db816b1b8238108c2bbb3a6fbc5
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 {'E','X','I','T','\0'}
99 static const WCHAR externals[][10] = {
100 {'A','T','T','R','I','B','\0'},
101 {'X','C','O','P','Y','\0'}
103 static const WCHAR onW[] = {'O','N','\0'};
104 static const WCHAR offW[] = {'O','F','F','\0'};
105 static const WCHAR parmY[] = {'/','Y','\0'};
106 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
107 static const WCHAR eqeqW[] = {'=','=','\0'};
109 static HINSTANCE hinst;
110 struct env_stack *saved_environment;
111 static BOOL verify_mode = FALSE;
113 /* set /a routines work from single character operators, but some of the
114 operators are multiple character ones, especially the assignment ones.
115 Temporarily represent these using the values below on the operator stack */
116 #define OP_POSITIVE 'P'
117 #define OP_NEGATIVE 'N'
118 #define OP_ASSSIGNMUL 'a'
119 #define OP_ASSSIGNDIV 'b'
120 #define OP_ASSSIGNMOD 'c'
121 #define OP_ASSSIGNADD 'd'
122 #define OP_ASSSIGNSUB 'e'
123 #define OP_ASSSIGNAND 'f'
124 #define OP_ASSSIGNNOT 'g'
125 #define OP_ASSSIGNOR 'h'
126 #define OP_ASSSIGNSHL 'i'
127 #define OP_ASSSIGNSHR 'j'
129 /* This maintains a stack of operators, holding both the operator precedence
130 and the single character representation of the operator in question */
131 typedef struct _OPSTACK
133 int precedence;
134 WCHAR op;
135 struct _OPSTACK *next;
136 } OPSTACK;
138 /* This maintains a stack of values, where each value can either be a
139 numeric value, or a string representing an environment variable */
140 typedef struct _VARSTACK
142 BOOL isnum;
143 WCHAR *variable;
144 int value;
145 struct _VARSTACK *next;
146 } VARSTACK;
148 /* This maintains a mapping between the calculated operator and the
149 single character representation for the assignment operators. */
150 static struct
152 WCHAR op;
153 WCHAR calculatedop;
154 } calcassignments[] =
156 {'*', OP_ASSSIGNMUL},
157 {'/', OP_ASSSIGNDIV},
158 {'%', OP_ASSSIGNMOD},
159 {'+', OP_ASSSIGNADD},
160 {'-', OP_ASSSIGNSUB},
161 {'&', OP_ASSSIGNAND},
162 {'^', OP_ASSSIGNNOT},
163 {'|', OP_ASSSIGNOR},
164 {'<', OP_ASSSIGNSHL},
165 {'>', OP_ASSSIGNSHR},
166 {' ',' '}
169 /**************************************************************************
170 * WCMD_ask_confirm
172 * Issue a message and ask for confirmation, waiting on a valid answer.
174 * Returns True if Y (or A) answer is selected
175 * If optionAll contains a pointer, ALL is allowed, and if answered
176 * set to TRUE
179 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
180 BOOL *optionAll) {
182 UINT msgid;
183 WCHAR confirm[MAXSTRING];
184 WCHAR options[MAXSTRING];
185 WCHAR Ybuffer[MAXSTRING];
186 WCHAR Nbuffer[MAXSTRING];
187 WCHAR Abuffer[MAXSTRING];
188 WCHAR answer[MAX_PATH] = {'\0'};
189 DWORD count = 0;
191 /* Load the translated valid answers */
192 if (showSureText)
193 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
194 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
195 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
196 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
197 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
198 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
200 /* Loop waiting on a valid answer */
201 if (optionAll)
202 *optionAll = FALSE;
203 while (1)
205 WCMD_output_asis (message);
206 if (showSureText)
207 WCMD_output_asis (confirm);
208 WCMD_output_asis (options);
209 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
210 answer[0] = toupperW(answer[0]);
211 if (answer[0] == Ybuffer[0])
212 return TRUE;
213 if (answer[0] == Nbuffer[0])
214 return FALSE;
215 if (optionAll && answer[0] == Abuffer[0])
217 *optionAll = TRUE;
218 return TRUE;
223 /****************************************************************************
224 * WCMD_clear_screen
226 * Clear the terminal screen.
229 void WCMD_clear_screen (void) {
231 /* Emulate by filling the screen from the top left to bottom right with
232 spaces, then moving the cursor to the top left afterwards */
233 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
234 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
236 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
238 COORD topLeft;
239 DWORD screenSize, written;
241 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
243 topLeft.X = 0;
244 topLeft.Y = 0;
245 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
246 FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
247 SetConsoleCursorPosition(hStdOut, topLeft);
251 /****************************************************************************
252 * WCMD_change_tty
254 * Change the default i/o device (ie redirect STDin/STDout).
257 void WCMD_change_tty (void) {
259 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
263 /****************************************************************************
264 * WCMD_choice
268 void WCMD_choice (const WCHAR * args) {
270 static const WCHAR bellW[] = {7,0};
271 static const WCHAR commaW[] = {',',0};
272 static const WCHAR bracket_open[] = {'[',0};
273 static const WCHAR bracket_close[] = {']','?',0};
274 WCHAR answer[16];
275 WCHAR buffer[16];
276 WCHAR *ptr = NULL;
277 WCHAR *opt_c = NULL;
278 WCHAR *my_command = NULL;
279 WCHAR opt_default = 0;
280 DWORD opt_timeout = 0;
281 DWORD count;
282 DWORD oldmode;
283 BOOL have_console;
284 BOOL opt_n = FALSE;
285 BOOL opt_s = FALSE;
287 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
288 errorlevel = 0;
290 my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
292 ptr = WCMD_skip_leading_spaces(my_command);
293 while (*ptr == '/') {
294 switch (toupperW(ptr[1])) {
295 case 'C':
296 ptr += 2;
297 /* the colon is optional */
298 if (*ptr == ':')
299 ptr++;
301 if (!*ptr || isspaceW(*ptr)) {
302 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
303 heap_free(my_command);
304 return;
307 /* remember the allowed keys (overwrite previous /C option) */
308 opt_c = ptr;
309 while (*ptr && (!isspaceW(*ptr)))
310 ptr++;
312 if (*ptr) {
313 /* terminate allowed chars */
314 *ptr = 0;
315 ptr = WCMD_skip_leading_spaces(&ptr[1]);
317 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
318 break;
320 case 'N':
321 opt_n = TRUE;
322 ptr = WCMD_skip_leading_spaces(&ptr[2]);
323 break;
325 case 'S':
326 opt_s = TRUE;
327 ptr = WCMD_skip_leading_spaces(&ptr[2]);
328 break;
330 case 'T':
331 ptr = &ptr[2];
332 /* the colon is optional */
333 if (*ptr == ':')
334 ptr++;
336 opt_default = *ptr++;
338 if (!opt_default || (*ptr != ',')) {
339 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
340 heap_free(my_command);
341 return;
343 ptr++;
345 count = 0;
346 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
347 count++;
348 ptr++;
351 answer[count] = 0;
352 opt_timeout = atoiW(answer);
354 ptr = WCMD_skip_leading_spaces(ptr);
355 break;
357 default:
358 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
359 heap_free(my_command);
360 return;
364 if (opt_timeout)
365 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
367 if (have_console)
368 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
370 /* use default keys, when needed: localized versions of "Y"es and "No" */
371 if (!opt_c) {
372 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
373 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
374 opt_c = buffer;
375 buffer[2] = 0;
378 /* print the question, when needed */
379 if (*ptr)
380 WCMD_output_asis(ptr);
382 if (!opt_s) {
383 struprW(opt_c);
384 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
387 if (!opt_n) {
388 /* print a list of all allowed answers inside brackets */
389 WCMD_output_asis(bracket_open);
390 ptr = opt_c;
391 answer[1] = 0;
392 while ((answer[0] = *ptr++)) {
393 WCMD_output_asis(answer);
394 if (*ptr)
395 WCMD_output_asis(commaW);
397 WCMD_output_asis(bracket_close);
400 while (TRUE) {
402 /* FIXME: Add support for option /T */
403 answer[1] = 0; /* terminate single character string */
404 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
406 if (!opt_s)
407 answer[0] = toupperW(answer[0]);
409 ptr = strchrW(opt_c, answer[0]);
410 if (ptr) {
411 WCMD_output_asis(answer);
412 WCMD_output_asis(newlineW);
413 if (have_console)
414 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
416 errorlevel = (ptr - opt_c) + 1;
417 WINE_TRACE("answer: %d\n", errorlevel);
418 heap_free(my_command);
419 return;
421 else
423 /* key not allowed: play the bell */
424 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
425 WCMD_output_asis(bellW);
430 /****************************************************************************
431 * WCMD_AppendEOF
433 * Adds an EOF onto the end of a file
434 * Returns TRUE on success
436 static BOOL WCMD_AppendEOF(WCHAR *filename)
438 HANDLE h;
439 DWORD bytes_written;
441 char eof = '\x1a';
443 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
444 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
445 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
447 if (h == NULL) {
448 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
449 return FALSE;
450 } else {
451 SetFilePointer (h, 0, NULL, FILE_END);
452 if (!WriteFile(h, &eof, 1, &bytes_written, NULL)) {
453 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
454 CloseHandle(h);
455 return FALSE;
457 CloseHandle(h);
459 return TRUE;
462 /****************************************************************************
463 * WCMD_ManualCopy
465 * Copies from a file
466 * optionally reading only until EOF (ascii copy)
467 * optionally appending onto an existing file (append)
468 * Returns TRUE on success
470 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
472 HANDLE in,out;
473 BOOL ok;
474 DWORD bytesread, byteswritten;
476 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
477 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
479 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
480 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
481 if (in == NULL) {
482 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
483 return FALSE;
486 /* Open the output file, overwriting if not appending */
487 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
488 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
489 if (out == NULL) {
490 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
491 CloseHandle(in);
492 return FALSE;
495 /* Move to end of destination if we are going to append to it */
496 if (append) {
497 SetFilePointer(out, 0, NULL, FILE_END);
500 /* Loop copying data from source to destination until EOF read */
503 char buffer[MAXSTRING];
505 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
506 if (ok) {
508 /* Stop at first EOF */
509 if (ascii) {
510 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
511 if (ptr) bytesread = (ptr - buffer);
514 if (bytesread) {
515 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
516 if (!ok || byteswritten != bytesread) {
517 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
518 wine_dbgstr_w(dstname), GetLastError());
521 } else {
522 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
523 wine_dbgstr_w(srcname), GetLastError());
525 } while (ok && bytesread > 0);
527 CloseHandle(out);
528 CloseHandle(in);
529 return ok;
532 /****************************************************************************
533 * WCMD_copy
535 * Copy a file or wildcarded set.
536 * For ascii/binary type copies, it gets complex:
537 * Syntax on command line is
538 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
539 * Where first /a or /b sets 'mode in operation' until another is found
540 * once another is found, it applies to the file preceding the /a or /b
541 * In addition each filename can contain wildcards
542 * To make matters worse, the + may be in the same parameter (i.e. no
543 * whitespace) or with whitespace separating it
545 * ASCII mode on read == read and stop at first EOF
546 * ASCII mode on write == append EOF to destination
547 * Binary == copy as-is
549 * Design of this is to build up a list of files which will be copied into a
550 * list, then work through the list file by file.
551 * If no destination is specified, it defaults to the name of the first file in
552 * the list, but the current directory.
556 void WCMD_copy(WCHAR * args) {
558 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
559 WCHAR *thisparam;
560 int argno = 0;
561 WCHAR *rawarg;
562 WIN32_FIND_DATAW fd;
563 HANDLE hff = INVALID_HANDLE_VALUE;
564 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
565 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
566 BOOL anyconcats = FALSE; /* Have we found any + options */
567 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
568 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
569 BOOL prompt; /* Prompt before overwriting */
570 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
571 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
572 BOOL status;
573 WCHAR copycmd[4];
574 DWORD len;
575 BOOL dstisdevice = FALSE;
576 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
578 typedef struct _COPY_FILES
580 struct _COPY_FILES *next;
581 BOOL concatenate;
582 WCHAR *name;
583 int binarycopy;
584 } COPY_FILES;
585 COPY_FILES *sourcelist = NULL;
586 COPY_FILES *lastcopyentry = NULL;
587 COPY_FILES *destination = NULL;
588 COPY_FILES *thiscopy = NULL;
589 COPY_FILES *prevcopy = NULL;
591 /* Assume we were successful! */
592 errorlevel = 0;
594 /* If no args supplied at all, report an error */
595 if (param1[0] == 0x00) {
596 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
597 errorlevel = 1;
598 return;
601 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
603 /* Walk through all args, building up a list of files to process */
604 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
605 while (*(thisparam)) {
606 WCHAR *pos1, *pos2;
607 BOOL inquotes;
609 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
611 /* Handle switches */
612 if (*thisparam == '/') {
613 while (*thisparam == '/') {
614 thisparam++;
615 if (toupperW(*thisparam) == 'D') {
616 opt_d = TRUE;
617 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
618 } else if (toupperW(*thisparam) == 'Y') {
619 opt_y = TRUE;
620 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
621 opt_noty = TRUE;
622 } else if (toupperW(*thisparam) == 'V') {
623 opt_v = TRUE;
624 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
625 } else if (toupperW(*thisparam) == 'N') {
626 opt_n = TRUE;
627 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
628 } else if (toupperW(*thisparam) == 'Z') {
629 opt_z = TRUE;
630 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
631 } else if (toupperW(*thisparam) == 'A') {
632 if (binarymode != 0) {
633 binarymode = 0;
634 WINE_TRACE("Subsequent files will be handled as ASCII\n");
635 if (destination != NULL) {
636 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
637 destination->binarycopy = binarymode;
638 } else if (lastcopyentry != NULL) {
639 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
640 lastcopyentry->binarycopy = binarymode;
643 } else if (toupperW(*thisparam) == 'B') {
644 if (binarymode != 1) {
645 binarymode = 1;
646 WINE_TRACE("Subsequent files will be handled as binary\n");
647 if (destination != NULL) {
648 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
649 destination->binarycopy = binarymode;
650 } else if (lastcopyentry != NULL) {
651 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
652 lastcopyentry->binarycopy = binarymode;
655 } else {
656 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
658 thisparam++;
661 /* This parameter was purely switches, get the next one */
662 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
663 continue;
666 /* We have found something which is not a switch. If could be anything of the form
667 sourcefilename (which could be destination too)
668 + (when filename + filename syntex used)
669 sourcefilename+sourcefilename
670 +sourcefilename
671 +/b[tests show windows then ignores to end of parameter]
674 if (*thisparam=='+') {
675 if (lastcopyentry == NULL) {
676 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
677 errorlevel = 1;
678 goto exitreturn;
679 } else {
680 concatnextfilename = TRUE;
681 anyconcats = TRUE;
684 /* Move to next thing to process */
685 thisparam++;
686 if (*thisparam == 0x00)
687 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
688 continue;
691 /* We have found something to process - build a COPY_FILE block to store it */
692 thiscopy = heap_alloc(sizeof(COPY_FILES));
694 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
695 thiscopy->concatenate = concatnextfilename;
696 thiscopy->binarycopy = binarymode;
697 thiscopy->next = NULL;
699 /* Time to work out the name. Allocate at least enough space (deliberately too much to
700 leave space to append \* to the end) , then copy in character by character. Strip off
701 quotes if we find them. */
702 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
703 thiscopy->name = heap_alloc(len*sizeof(WCHAR));
704 memset(thiscopy->name, 0x00, len);
706 pos1 = thisparam;
707 pos2 = thiscopy->name;
708 inquotes = FALSE;
709 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
710 if (*pos1 == '"') {
711 inquotes = !inquotes;
712 pos1++;
713 } else *pos2++ = *pos1++;
715 *pos2 = 0;
716 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
718 /* This is either the first source, concatenated subsequent source or destination */
719 if (sourcelist == NULL) {
720 WINE_TRACE("Adding as first source part\n");
721 sourcelist = thiscopy;
722 lastcopyentry = thiscopy;
723 } else if (concatnextfilename) {
724 WINE_TRACE("Adding to source file list to be concatenated\n");
725 lastcopyentry->next = thiscopy;
726 lastcopyentry = thiscopy;
727 } else if (destination == NULL) {
728 destination = thiscopy;
729 } else {
730 /* We have processed sources and destinations and still found more to do - invalid */
731 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
732 errorlevel = 1;
733 goto exitreturn;
735 concatnextfilename = FALSE;
737 /* We either need to process the rest of the parameter or move to the next */
738 if (*pos1 == '/' || *pos1 == '+') {
739 thisparam = pos1;
740 continue;
741 } else {
742 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
746 /* Ensure we have at least one source file */
747 if (!sourcelist) {
748 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
749 errorlevel = 1;
750 goto exitreturn;
753 /* Default whether automatic overwriting is on. If we are interactive then
754 we prompt by default, otherwise we overwrite by default
755 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
756 if (opt_noty) prompt = TRUE;
757 else if (opt_y) prompt = FALSE;
758 else {
759 /* By default, we will force the overwrite in batch mode and ask for
760 * confirmation in interactive mode. */
761 prompt = interactive;
762 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
763 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
764 * default behavior. */
765 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
766 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
767 if (!lstrcmpiW (copycmd, parmY))
768 prompt = FALSE;
769 else if (!lstrcmpiW (copycmd, parmNoY))
770 prompt = TRUE;
774 /* Calculate the destination now - if none supplied, it's current dir +
775 filename of first file in list*/
776 if (destination == NULL) {
778 WINE_TRACE("No destination supplied, so need to calculate it\n");
779 strcpyW(destname, dotW);
780 strcatW(destname, slashW);
782 destination = heap_alloc(sizeof(COPY_FILES));
783 if (destination == NULL) goto exitreturn;
784 destination->concatenate = FALSE; /* Not used for destination */
785 destination->binarycopy = binarymode;
786 destination->next = NULL; /* Not used for destination */
787 destination->name = NULL; /* To be filled in */
788 destisdirectory = TRUE;
790 } else {
791 WCHAR *filenamepart;
792 DWORD attributes;
794 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
796 /* Convert to fully qualified path/filename */
797 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
798 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
800 /* If parameter is a directory, ensure it ends in \ */
801 attributes = GetFileAttributesW(destname);
802 if (ends_with_backslash( destname ) ||
803 ((attributes != INVALID_FILE_ATTRIBUTES) &&
804 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
806 destisdirectory = TRUE;
807 if (!ends_with_backslash( destname )) strcatW(destname, slashW);
808 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
812 /* Normally, the destination is the current directory unless we are
813 concatenating, in which case it's current directory plus first filename.
814 Note that if the
815 In addition by default it is a binary copy unless concatenating, when
816 the copy defaults to an ascii copy (stop at EOF). We do not know the
817 first source part yet (until we search) so flag as needing filling in. */
819 if (anyconcats) {
820 /* We have found an a+b type syntax, so destination has to be a filename
821 and we need to default to ascii copying. If we have been supplied a
822 directory as the destination, we need to defer calculating the name */
823 if (destisdirectory) appendfirstsource = TRUE;
824 if (destination->binarycopy == -1) destination->binarycopy = 0;
826 } else if (!destisdirectory) {
827 /* We have been asked to copy to a filename. Default to ascii IF the
828 source contains wildcards (true even if only one match) */
829 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
830 anyconcats = TRUE; /* We really are concatenating to a single file */
831 if (destination->binarycopy == -1) {
832 destination->binarycopy = 0;
834 } else {
835 if (destination->binarycopy == -1) {
836 destination->binarycopy = 1;
841 /* Save away the destination name*/
842 heap_free(destination->name);
843 destination->name = heap_strdupW(destname);
844 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
845 wine_dbgstr_w(destname), appendfirstsource);
847 /* Remember if the destination is a device */
848 if (strncmpW(destination->name, deviceW, strlenW(deviceW)) == 0) {
849 WINE_TRACE("Destination is a device\n");
850 dstisdevice = TRUE;
853 /* Now we need to walk the set of sources, and process each name we come to.
854 If anyconcats is true, we are writing to one file, otherwise we are using
855 the source name each time.
856 If destination exists, prompt for overwrite the first time (if concatenating
857 we ask each time until yes is answered)
858 The first source file we come across must exist (when wildcards expanded)
859 and if concatenating with overwrite prompts, each source file must exist
860 until a yes is answered. */
862 thiscopy = sourcelist;
863 prevcopy = NULL;
865 while (thiscopy != NULL) {
867 WCHAR srcpath[MAX_PATH];
868 const WCHAR *srcname;
869 WCHAR *filenamepart;
870 DWORD attributes;
871 BOOL srcisdevice = FALSE;
873 /* If it was not explicit, we now know whether we are concatenating or not and
874 hence whether to copy as binary or ascii */
875 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
877 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
878 to where the filename portion begins (used for wildcard expansion). */
879 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
880 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
882 /* If parameter is a directory, ensure it ends in \* */
883 attributes = GetFileAttributesW(srcpath);
884 if (ends_with_backslash( srcpath )) {
886 /* We need to know where the filename part starts, so append * and
887 recalculate the full resulting path */
888 strcatW(thiscopy->name, starW);
889 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
890 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
892 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
893 (attributes != INVALID_FILE_ATTRIBUTES) &&
894 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
896 /* We need to know where the filename part starts, so append \* and
897 recalculate the full resulting path */
898 strcatW(thiscopy->name, slashstarW);
899 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
900 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
903 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
904 wine_dbgstr_w(srcpath), anyconcats);
906 /* If the source is a device, just use it, otherwise search */
907 if (strncmpW(srcpath, deviceW, strlenW(deviceW)) == 0) {
908 WINE_TRACE("Source is a device\n");
909 srcisdevice = TRUE;
910 srcname = &srcpath[4]; /* After the \\.\ prefix */
911 } else {
913 /* Loop through all source files */
914 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
915 hff = FindFirstFileW(srcpath, &fd);
916 if (hff != INVALID_HANDLE_VALUE) {
917 srcname = fd.cFileName;
921 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
922 do {
923 WCHAR outname[MAX_PATH];
924 BOOL overwrite;
926 /* Skip . and .., and directories */
927 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
928 WINE_TRACE("Skipping directories\n");
929 } else {
931 /* Build final destination name */
932 strcpyW(outname, destination->name);
933 if (destisdirectory || appendfirstsource) strcatW(outname, srcname);
935 /* Build source name */
936 if (!srcisdevice) strcpyW(filenamepart, srcname);
938 /* Do we just overwrite (we do if we are writing to a device) */
939 overwrite = !prompt;
940 if (dstisdevice || (anyconcats && writtenoneconcat)) {
941 overwrite = TRUE;
944 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
945 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
946 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
947 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
949 /* Prompt before overwriting */
950 if (!overwrite) {
951 DWORD attributes = GetFileAttributesW(outname);
952 if (attributes != INVALID_FILE_ATTRIBUTES) {
953 WCHAR* question;
954 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
955 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
956 LocalFree(question);
958 else overwrite = TRUE;
961 /* If we needed to save away the first filename, do it */
962 if (appendfirstsource && overwrite) {
963 heap_free(destination->name);
964 destination->name = heap_strdupW(outname);
965 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
966 appendfirstsource = FALSE;
967 destisdirectory = FALSE;
970 /* Do the copy as appropriate */
971 if (overwrite) {
972 if (anyconcats && writtenoneconcat) {
973 if (thiscopy->binarycopy) {
974 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
975 } else {
976 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
978 } else if (!thiscopy->binarycopy) {
979 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
980 } else if (srcisdevice) {
981 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
982 } else {
983 status = CopyFileW(srcpath, outname, FALSE);
985 if (!status) {
986 WCMD_print_error ();
987 errorlevel = 1;
988 } else {
989 WINE_TRACE("Copied successfully\n");
990 if (anyconcats) writtenoneconcat = TRUE;
992 /* Append EOF if ascii destination and we are not going to add more onto the end
993 Note: Testing shows windows has an optimization whereas if you have a binary
994 copy of a file to a single destination (ie concatenation) then it does not add
995 the EOF, hence the check on the source copy type below. */
996 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
997 if (!WCMD_AppendEOF(outname)) {
998 WCMD_print_error ();
999 errorlevel = 1;
1005 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1006 if (!srcisdevice) FindClose (hff);
1007 } else {
1008 /* Error if the first file was not found */
1009 if (!anyconcats || !writtenoneconcat) {
1010 WCMD_print_error ();
1011 errorlevel = 1;
1015 /* Step on to the next supplied source */
1016 thiscopy = thiscopy -> next;
1019 /* Append EOF if ascii destination and we were concatenating */
1020 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
1021 if (!WCMD_AppendEOF(destination->name)) {
1022 WCMD_print_error ();
1023 errorlevel = 1;
1027 /* Exit out of the routine, freeing any remaining allocated memory */
1028 exitreturn:
1030 thiscopy = sourcelist;
1031 while (thiscopy != NULL) {
1032 prevcopy = thiscopy;
1033 /* Free up this block*/
1034 thiscopy = thiscopy -> next;
1035 heap_free(prevcopy->name);
1036 heap_free(prevcopy);
1039 /* Free up the destination memory */
1040 if (destination) {
1041 heap_free(destination->name);
1042 heap_free(destination);
1045 return;
1048 /****************************************************************************
1049 * WCMD_create_dir
1051 * Create a directory (and, if needed, any intermediate directories).
1053 * Modifies its argument by replacing slashes temporarily with nulls.
1056 static BOOL create_full_path(WCHAR* path)
1058 WCHAR *p, *start;
1060 /* don't mess with drive letter portion of path, if any */
1061 start = path;
1062 if (path[1] == ':')
1063 start = path+2;
1065 /* Strip trailing slashes. */
1066 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
1067 *p = 0;
1069 /* Step through path, creating intermediate directories as needed. */
1070 /* First component includes drive letter, if any. */
1071 p = start;
1072 for (;;) {
1073 DWORD rv;
1074 /* Skip to end of component */
1075 while (*p == '\\') p++;
1076 while (*p && *p != '\\') p++;
1077 if (!*p) {
1078 /* path is now the original full path */
1079 return CreateDirectoryW(path, NULL);
1081 /* Truncate path, create intermediate directory, and restore path */
1082 *p = 0;
1083 rv = CreateDirectoryW(path, NULL);
1084 *p = '\\';
1085 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1086 return FALSE;
1088 /* notreached */
1089 return FALSE;
1092 void WCMD_create_dir (WCHAR *args) {
1093 int argno = 0;
1094 WCHAR *argN = args;
1096 if (param1[0] == 0x00) {
1097 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1098 return;
1100 /* Loop through all args */
1101 while (TRUE) {
1102 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1103 if (!argN) break;
1104 if (!create_full_path(thisArg)) {
1105 WCMD_print_error ();
1106 errorlevel = 1;
1111 /* Parse the /A options given by the user on the commandline
1112 * into a bitmask of wanted attributes (*wantSet),
1113 * and a bitmask of unwanted attributes (*wantClear).
1115 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1116 static const WCHAR parmA[] = {'/','A','\0'};
1117 WCHAR *p;
1119 /* both are strictly 'out' parameters */
1120 *wantSet=0;
1121 *wantClear=0;
1123 /* For each /A argument */
1124 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1125 /* Skip /A itself */
1126 p += 2;
1128 /* Skip optional : */
1129 if (*p == ':') p++;
1131 /* For each of the attribute specifier chars to this /A option */
1132 for (; *p != 0 && *p != '/'; p++) {
1133 BOOL negate = FALSE;
1134 DWORD mask = 0;
1136 if (*p == '-') {
1137 negate=TRUE;
1138 p++;
1141 /* Convert the attribute specifier to a bit in one of the masks */
1142 switch (*p) {
1143 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1144 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1145 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1146 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1147 default:
1148 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1150 if (negate)
1151 *wantClear |= mask;
1152 else
1153 *wantSet |= mask;
1158 /* If filename part of parameter is * or *.*,
1159 * and neither /Q nor /P options were given,
1160 * prompt the user whether to proceed.
1161 * Returns FALSE if user says no, TRUE otherwise.
1162 * *pPrompted is set to TRUE if the user is prompted.
1163 * (If /P supplied, del will prompt for individual files later.)
1165 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1166 static const WCHAR parmP[] = {'/','P','\0'};
1167 static const WCHAR parmQ[] = {'/','Q','\0'};
1169 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1170 static const WCHAR anyExt[]= {'.','*','\0'};
1171 WCHAR drive[10];
1172 WCHAR dir[MAX_PATH];
1173 WCHAR fname[MAX_PATH];
1174 WCHAR ext[MAX_PATH];
1175 WCHAR fpath[MAX_PATH];
1177 /* Convert path into actual directory spec */
1178 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1179 WCMD_splitpath(fpath, drive, dir, fname, ext);
1181 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1182 if ((strcmpW(fname, starW) == 0) &&
1183 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1185 WCHAR question[MAXSTRING];
1186 static const WCHAR fmt[] = {'%','s',' ','\0'};
1188 /* Caller uses this to suppress "file not found" warning later */
1189 *pPrompted = TRUE;
1191 /* Ask for confirmation */
1192 wsprintfW(question, fmt, fpath);
1193 return WCMD_ask_confirm(question, TRUE, NULL);
1196 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1197 return TRUE;
1200 /* Helper function for WCMD_delete().
1201 * Deletes a single file, directory, or wildcard.
1202 * If /S was given, does it recursively.
1203 * Returns TRUE if a file was deleted.
1205 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1207 static const WCHAR parmP[] = {'/','P','\0'};
1208 static const WCHAR parmS[] = {'/','S','\0'};
1209 static const WCHAR parmF[] = {'/','F','\0'};
1210 DWORD wanted_attrs;
1211 DWORD unwanted_attrs;
1212 BOOL found = FALSE;
1213 WCHAR argCopy[MAX_PATH];
1214 WIN32_FIND_DATAW fd;
1215 HANDLE hff;
1216 WCHAR fpath[MAX_PATH];
1217 WCHAR *p;
1218 BOOL handleParm = TRUE;
1220 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1222 strcpyW(argCopy, thisArg);
1223 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1224 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1226 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1227 /* Skip this arg if user declines to delete *.* */
1228 return FALSE;
1231 /* First, try to delete in the current directory */
1232 hff = FindFirstFileW(argCopy, &fd);
1233 if (hff == INVALID_HANDLE_VALUE) {
1234 handleParm = FALSE;
1235 } else {
1236 found = TRUE;
1239 /* Support del <dirname> by just deleting all files dirname\* */
1240 if (handleParm
1241 && (strchrW(argCopy,'*') == NULL)
1242 && (strchrW(argCopy,'?') == NULL)
1243 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1245 WCHAR modifiedParm[MAX_PATH];
1246 static const WCHAR slashStar[] = {'\\','*','\0'};
1248 strcpyW(modifiedParm, argCopy);
1249 strcatW(modifiedParm, slashStar);
1250 FindClose(hff);
1251 found = TRUE;
1252 WCMD_delete_one(modifiedParm);
1254 } else if (handleParm) {
1256 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1257 strcpyW (fpath, argCopy);
1258 do {
1259 p = strrchrW (fpath, '\\');
1260 if (p != NULL) {
1261 *++p = '\0';
1262 strcatW (fpath, fd.cFileName);
1264 else strcpyW (fpath, fd.cFileName);
1265 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1266 BOOL ok;
1268 /* Handle attribute matching (/A) */
1269 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1270 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1272 /* /P means prompt for each file */
1273 if (ok && strstrW (quals, parmP) != NULL) {
1274 WCHAR* question;
1276 /* Ask for confirmation */
1277 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1278 ok = WCMD_ask_confirm(question, FALSE, NULL);
1279 LocalFree(question);
1282 /* Only proceed if ok to */
1283 if (ok) {
1285 /* If file is read only, and /A:r or /F supplied, delete it */
1286 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1287 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1288 strstrW (quals, parmF) != NULL)) {
1289 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1292 /* Now do the delete */
1293 if (!DeleteFileW(fpath)) WCMD_print_error ();
1297 } while (FindNextFileW(hff, &fd) != 0);
1298 FindClose (hff);
1301 /* Now recurse into all subdirectories handling the parameter in the same way */
1302 if (strstrW (quals, parmS) != NULL) {
1304 WCHAR thisDir[MAX_PATH];
1305 int cPos;
1307 WCHAR drive[10];
1308 WCHAR dir[MAX_PATH];
1309 WCHAR fname[MAX_PATH];
1310 WCHAR ext[MAX_PATH];
1312 /* Convert path into actual directory spec */
1313 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1314 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1316 strcpyW(thisDir, drive);
1317 strcatW(thisDir, dir);
1318 cPos = strlenW(thisDir);
1320 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1322 /* Append '*' to the directory */
1323 thisDir[cPos] = '*';
1324 thisDir[cPos+1] = 0x00;
1326 hff = FindFirstFileW(thisDir, &fd);
1328 /* Remove residual '*' */
1329 thisDir[cPos] = 0x00;
1331 if (hff != INVALID_HANDLE_VALUE) {
1332 DIRECTORY_STACK *allDirs = NULL;
1333 DIRECTORY_STACK *lastEntry = NULL;
1335 do {
1336 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1337 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1338 (strcmpW(fd.cFileName, dotW) != 0)) {
1340 DIRECTORY_STACK *nextDir;
1341 WCHAR subParm[MAX_PATH];
1343 /* Work out search parameter in sub dir */
1344 strcpyW (subParm, thisDir);
1345 strcatW (subParm, fd.cFileName);
1346 strcatW (subParm, slashW);
1347 strcatW (subParm, fname);
1348 strcatW (subParm, ext);
1349 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1351 /* Allocate memory, add to list */
1352 nextDir = heap_alloc(sizeof(DIRECTORY_STACK));
1353 if (allDirs == NULL) allDirs = nextDir;
1354 if (lastEntry != NULL) lastEntry->next = nextDir;
1355 lastEntry = nextDir;
1356 nextDir->next = NULL;
1357 nextDir->dirName = heap_strdupW(subParm);
1359 } while (FindNextFileW(hff, &fd) != 0);
1360 FindClose (hff);
1362 /* Go through each subdir doing the delete */
1363 while (allDirs != NULL) {
1364 DIRECTORY_STACK *tempDir;
1366 tempDir = allDirs->next;
1367 found |= WCMD_delete_one (allDirs->dirName);
1369 heap_free(allDirs->dirName);
1370 heap_free(allDirs);
1371 allDirs = tempDir;
1376 return found;
1379 /****************************************************************************
1380 * WCMD_delete
1382 * Delete a file or wildcarded set.
1384 * Note on /A:
1385 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1386 * - Each set is a pattern, eg /ahr /as-r means
1387 * readonly+hidden OR nonreadonly system files
1388 * - The '-' applies to a single field, ie /a:-hr means read only
1389 * non-hidden files
1392 BOOL WCMD_delete (WCHAR *args) {
1393 int argno;
1394 WCHAR *argN;
1395 BOOL argsProcessed = FALSE;
1396 BOOL foundAny = FALSE;
1398 errorlevel = 0;
1400 for (argno=0; ; argno++) {
1401 BOOL found;
1402 WCHAR *thisArg;
1404 argN = NULL;
1405 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1406 if (!argN)
1407 break; /* no more parameters */
1408 if (argN[0] == '/')
1409 continue; /* skip options */
1411 argsProcessed = TRUE;
1412 found = WCMD_delete_one(thisArg);
1413 if (!found)
1414 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1415 foundAny |= found;
1418 /* Handle no valid args */
1419 if (!argsProcessed)
1420 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1422 return foundAny;
1426 * WCMD_strtrim
1428 * Returns a trimmed version of s with all leading and trailing whitespace removed
1429 * Pre: s non NULL
1432 static WCHAR *WCMD_strtrim(const WCHAR *s)
1434 DWORD len = strlenW(s);
1435 const WCHAR *start = s;
1436 WCHAR* result;
1438 result = heap_alloc((len + 1) * sizeof(WCHAR));
1440 while (isspaceW(*start)) start++;
1441 if (*start) {
1442 const WCHAR *end = s + len - 1;
1443 while (end > start && isspaceW(*end)) end--;
1444 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1445 result[end - start + 1] = '\0';
1446 } else {
1447 result[0] = '\0';
1450 return result;
1453 /****************************************************************************
1454 * WCMD_echo
1456 * Echo input to the screen (or not). We don't try to emulate the bugs
1457 * in DOS (try typing "ECHO ON AGAIN" for an example).
1460 void WCMD_echo (const WCHAR *args)
1462 int count;
1463 const WCHAR *origcommand = args;
1464 WCHAR *trimmed;
1466 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1467 || args[0]==':' || args[0]==';' || args[0]=='/')
1468 args++;
1470 trimmed = WCMD_strtrim(args);
1471 if (!trimmed) return;
1473 count = strlenW(trimmed);
1474 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1475 && origcommand[0]!=';' && origcommand[0]!='/') {
1476 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1477 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1478 heap_free(trimmed);
1479 return;
1482 if (lstrcmpiW(trimmed, onW) == 0)
1483 echo_mode = TRUE;
1484 else if (lstrcmpiW(trimmed, offW) == 0)
1485 echo_mode = FALSE;
1486 else {
1487 WCMD_output_asis (args);
1488 WCMD_output_asis (newlineW);
1490 heap_free(trimmed);
1493 /*****************************************************************************
1494 * WCMD_part_execute
1496 * Execute a command, and any && or bracketed follow on to the command. The
1497 * first command to be executed may not be at the front of the
1498 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1500 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1501 BOOL isIF, BOOL executecmds)
1503 CMD_LIST *curPosition = *cmdList;
1504 int myDepth = (*cmdList)->bracketDepth;
1506 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d)\n", cmdList, wine_dbgstr_w(firstcmd),
1507 executecmds);
1509 /* Skip leading whitespace between condition and the command */
1510 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1512 /* Process the first command, if there is one */
1513 if (executecmds && firstcmd && *firstcmd) {
1514 WCHAR *command = heap_strdupW(firstcmd);
1515 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1516 heap_free(command);
1520 /* If it didn't move the position, step to next command */
1521 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1523 /* Process any other parts of the command */
1524 if (*cmdList) {
1525 BOOL processThese = executecmds;
1527 while (*cmdList) {
1528 static const WCHAR ifElse[] = {'e','l','s','e'};
1530 /* execute all appropriate commands */
1531 curPosition = *cmdList;
1533 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1534 *cmdList,
1535 (*cmdList)->prevDelim,
1536 (*cmdList)->bracketDepth, myDepth);
1538 /* Execute any statements appended to the line */
1539 /* FIXME: Only if previous call worked for && or failed for || */
1540 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1541 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1542 if (processThese && (*cmdList)->command) {
1543 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1544 cmdList, FALSE);
1546 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1548 /* Execute any appended to the statement with (...) */
1549 } else if ((*cmdList)->bracketDepth > myDepth) {
1550 if (processThese) {
1551 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1552 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1554 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1556 /* End of the command - does 'ELSE ' follow as the next command? */
1557 } else {
1558 if (isIF
1559 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1560 (*cmdList)->command)) {
1562 /* Swap between if and else processing */
1563 processThese = !executecmds;
1565 /* Process the ELSE part */
1566 if (processThese) {
1567 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1568 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1570 /* Skip leading whitespace between condition and the command */
1571 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1572 if (*cmd) {
1573 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1576 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1577 } else {
1578 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1579 break;
1584 return;
1587 /*****************************************************************************
1588 * WCMD_parse_forf_options
1590 * Parses the for /f 'options', extracting the values and validating the
1591 * keywords. Note all keywords are optional.
1592 * Parameters:
1593 * options [I] The unparsed parameter string
1594 * eol [O] Set to the comment character (eol=x)
1595 * skip [O] Set to the number of lines to skip (skip=xx)
1596 * delims [O] Set to the token delimiters (delims=)
1597 * tokens [O] Set to the requested tokens, as provided (tokens=)
1598 * usebackq [O] Set to TRUE if usebackq found
1600 * Returns TRUE on success, FALSE on syntax error
1603 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1604 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1607 WCHAR *pos = options;
1608 int len = strlenW(pos);
1609 static const WCHAR eolW[] = {'e','o','l','='};
1610 static const WCHAR skipW[] = {'s','k','i','p','='};
1611 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1612 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1613 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1614 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1615 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1617 /* Initialize to defaults */
1618 strcpyW(delims, forf_defaultdelims);
1619 strcpyW(tokens, forf_defaulttokens);
1620 *eol = 0;
1621 *skip = 0;
1622 *usebackq = FALSE;
1624 /* Strip (optional) leading and trailing quotes */
1625 if ((*pos == '"') && (pos[len-1] == '"')) {
1626 pos[len-1] = 0;
1627 pos++;
1630 /* Process each keyword */
1631 while (pos && *pos) {
1632 if (*pos == ' ' || *pos == '\t') {
1633 pos++;
1635 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1636 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1637 pos, sizeof(eolW)/sizeof(WCHAR),
1638 eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1639 *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1640 pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1641 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1643 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1644 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1645 pos, sizeof(skipW)/sizeof(WCHAR),
1646 skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1647 WCHAR *nextchar = NULL;
1648 pos = pos + sizeof(skipW)/sizeof(WCHAR);
1649 *skip = strtoulW(pos, &nextchar, 0);
1650 WINE_TRACE("Found skip as %d lines\n", *skip);
1651 pos = nextchar;
1653 /* Save if usebackq semantics are in effect */
1654 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1655 pos, sizeof(usebackqW)/sizeof(WCHAR),
1656 usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1657 *usebackq = TRUE;
1658 pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1659 WINE_TRACE("Found usebackq\n");
1661 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1662 if you finish the optionsroot string with delims= otherwise the space is
1663 just a token delimiter! */
1664 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1665 pos, sizeof(delimsW)/sizeof(WCHAR),
1666 delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1667 int i=0;
1669 pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1670 while (*pos && *pos != ' ') {
1671 delims[i++] = *pos;
1672 pos++;
1674 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1675 delims[i++] = 0; /* Null terminate the delims */
1676 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1678 /* Save the tokens being requested */
1679 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1680 pos, sizeof(tokensW)/sizeof(WCHAR),
1681 tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1682 int i=0;
1684 pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1685 while (*pos && *pos != ' ') {
1686 tokens[i++] = *pos;
1687 pos++;
1689 tokens[i++] = 0; /* Null terminate the tokens */
1690 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1692 } else {
1693 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1694 return FALSE;
1697 return TRUE;
1700 /*****************************************************************************
1701 * WCMD_add_dirstowalk
1703 * When recursing through directories (for /r), we need to add to the list of
1704 * directories still to walk, any subdirectories of the one we are processing.
1706 * Parameters
1707 * options [I] The remaining list of directories still to process
1709 * Note this routine inserts the subdirectories found between the entry being
1710 * processed, and any other directory still to be processed, mimicking what
1711 * Windows does
1713 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1714 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1715 WCHAR fullitem[MAX_PATH];
1716 WIN32_FIND_DATAW fd;
1717 HANDLE hff;
1719 /* Build a generic search and add all directories on the list of directories
1720 still to walk */
1721 strcpyW(fullitem, dirsToWalk->dirName);
1722 strcatW(fullitem, slashstarW);
1723 hff = FindFirstFileW(fullitem, &fd);
1724 if (hff != INVALID_HANDLE_VALUE) {
1725 do {
1726 WINE_TRACE("Looking for subdirectories\n");
1727 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1728 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1729 (strcmpW(fd.cFileName, dotW) != 0))
1731 /* Allocate memory, add to list */
1732 DIRECTORY_STACK *toWalk = heap_alloc(sizeof(DIRECTORY_STACK));
1733 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1734 toWalk->next = remainingDirs->next;
1735 remainingDirs->next = toWalk;
1736 remainingDirs = toWalk;
1737 toWalk->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1738 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1739 strcatW(toWalk->dirName, slashW);
1740 strcatW(toWalk->dirName, fd.cFileName);
1741 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1742 toWalk, toWalk->next);
1744 } while (FindNextFileW(hff, &fd) != 0);
1745 WINE_TRACE("Finished adding all subdirectories\n");
1746 FindClose (hff);
1750 /**************************************************************************
1751 * WCMD_for_nexttoken
1753 * Parse the token= line, identifying the next highest number not processed
1754 * so far. Count how many tokens are referred (including duplicates) and
1755 * optionally return that, plus optionally indicate if the tokens= line
1756 * ends in a star.
1758 * Parameters:
1759 * lasttoken [I] - Identifies the token index of the last one
1760 * returned so far (-1 used for first loop)
1761 * tokenstr [I] - The specified tokens= line
1762 * firstCmd [O] - Optionally indicate how many tokens are listed
1763 * doAll [O] - Optionally indicate if line ends with *
1764 * duplicates [O] - Optionally indicate if there is any evidence of
1765 * overlaying tokens in the string
1766 * Note the caller should keep a running track of duplicates as the tokens
1767 * are recursively passed. If any have duplicates, then the * token should
1768 * not be honoured.
1770 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1771 int *totalfound, BOOL *doall,
1772 BOOL *duplicates)
1774 WCHAR *pos = tokenstr;
1775 int nexttoken = -1;
1777 if (totalfound) *totalfound = 0;
1778 if (doall) *doall = FALSE;
1779 if (duplicates) *duplicates = FALSE;
1781 WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken,
1782 wine_dbgstr_w(tokenstr), nexttoken);
1784 /* Loop through the token string, parsing it. Valid syntax is:
1785 token=m or x-y with comma delimiter and optionally * to finish*/
1786 while (*pos) {
1787 int nextnumber1, nextnumber2 = -1;
1788 WCHAR *nextchar;
1790 /* Get the next number */
1791 nextnumber1 = strtoulW(pos, &nextchar, 10);
1793 /* If it is followed by a minus, it's a range, so get the next one as well */
1794 if (*nextchar == '-') {
1795 nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1797 /* We want to return the lowest number that is higher than lasttoken
1798 but only if range is positive */
1799 if (nextnumber2 >= nextnumber1 &&
1800 lasttoken < nextnumber2) {
1802 int nextvalue;
1803 if (nexttoken == -1) {
1804 nextvalue = max(nextnumber1, (lasttoken+1));
1805 } else {
1806 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1809 /* Flag if duplicates identified */
1810 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1812 nexttoken = nextvalue;
1815 /* Update the running total for the whole range */
1816 if (nextnumber2 >= nextnumber1 && totalfound) {
1817 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1820 } else {
1821 if (totalfound) (*totalfound)++;
1823 /* See if the number found is one we have already seen */
1824 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1826 /* We want to return the lowest number that is higher than lasttoken */
1827 if (lasttoken < nextnumber1 &&
1828 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1829 nexttoken = nextnumber1;
1834 /* Remember if it is followed by a star, and if it is indicate a need to
1835 show all tokens, unless a duplicate has been found */
1836 if (*nextchar == '*') {
1837 if (doall) *doall = TRUE;
1838 if (totalfound) (*totalfound)++;
1841 /* Step on to the next character */
1842 pos = nextchar;
1843 if (*pos) pos++;
1846 /* Return result */
1847 if (nexttoken == -1) nexttoken = lasttoken;
1848 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1849 if (totalfound) WINE_TRACE("Found total tokens in total %d\n", *totalfound);
1850 if (doall && *doall) WINE_TRACE("Request for all tokens found\n");
1851 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1852 return nexttoken;
1855 /**************************************************************************
1856 * WCMD_parse_line
1858 * When parsing file or string contents (for /f), once the string to parse
1859 * has been identified, handle the various options and call the do part
1860 * if appropriate.
1862 * Parameters:
1863 * cmdStart [I] - Identifies the list of commands making up the
1864 * for loop body (especially if brackets in use)
1865 * firstCmd [I] - The textual start of the command after the DO
1866 * which is within the first item of cmdStart
1867 * cmdEnd [O] - Identifies where to continue after the DO
1868 * variable [I] - The variable identified on the for line
1869 * buffer [I] - The string to parse
1870 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1871 * forf_skip [I/O] - How many lines to skip first
1872 * forf_eol [I] - The 'end of line' (comment) character
1873 * forf_delims [I] - The delimiters to use when breaking the string apart
1874 * forf_tokens [I] - The tokens to use when breaking the string apart
1876 static void WCMD_parse_line(CMD_LIST *cmdStart,
1877 const WCHAR *firstCmd,
1878 CMD_LIST **cmdEnd,
1879 const WCHAR variable,
1880 WCHAR *buffer,
1881 BOOL *doExecuted,
1882 int *forf_skip,
1883 WCHAR forf_eol,
1884 WCHAR *forf_delims,
1885 WCHAR *forf_tokens) {
1887 WCHAR *parm;
1888 FOR_CONTEXT oldcontext;
1889 int varidx, varoffset;
1890 int nexttoken, lasttoken = -1;
1891 BOOL starfound = FALSE;
1892 BOOL thisduplicate = FALSE;
1893 BOOL anyduplicates = FALSE;
1894 int totalfound;
1896 /* Skip lines if requested */
1897 if (*forf_skip) {
1898 (*forf_skip)--;
1899 return;
1902 /* Save away any existing for variable context (e.g. nested for loops) */
1903 oldcontext = forloopcontext;
1905 /* Extract the parameters based on the tokens= value (There will always
1906 be some value, as if it is not supplied, it defaults to tokens=1).
1907 Rough logic:
1908 Count how many tokens are named in the line, identify the lowest
1909 Empty (set to null terminated string) that number of named variables
1910 While lasttoken != nextlowest
1911 %letter = parameter number 'nextlowest'
1912 letter++ (if >26 or >52 abort)
1913 Go through token= string finding next lowest number
1914 If token ends in * set %letter = raw position of token(nextnumber+1)
1916 lasttoken = -1;
1917 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1918 NULL, &thisduplicate);
1919 varidx = FOR_VAR_IDX(variable);
1921 /* Empty out variables */
1922 for (varoffset=0;
1923 varidx >= 0 && varoffset<totalfound && ((varidx+varoffset)%26);
1924 varoffset++) {
1925 forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
1926 /* Stop if we walk beyond z or Z */
1927 if (((varidx+varoffset) % 26) == 0) break;
1930 /* Loop extracting the tokens */
1931 varoffset = 0;
1932 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1933 while (varidx >= 0 && (nexttoken > lasttoken)) {
1934 anyduplicates |= thisduplicate;
1936 /* Extract the token number requested and set into the next variable context */
1937 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, FALSE, FALSE, forf_delims);
1938 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1939 varidx + varoffset, wine_dbgstr_w(parm));
1940 if (varidx >=0) {
1941 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1942 varoffset++;
1943 if (((varidx + varoffset) %26) == 0) break;
1946 /* Find the next token */
1947 lasttoken = nexttoken;
1948 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1949 &starfound, &thisduplicate);
1952 /* If all the rest of the tokens were requested, and there is still space in
1953 the variable range, write them now */
1954 if (!anyduplicates && starfound && varidx >= 0 && ((varidx+varoffset) % 26)) {
1955 nexttoken++;
1956 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
1957 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
1958 varidx + varoffset, wine_dbgstr_w(parm));
1959 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1962 /* Execute the body of the foor loop with these values */
1963 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
1964 CMD_LIST *thisCmdStart = cmdStart;
1965 *doExecuted = TRUE;
1966 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
1967 *cmdEnd = thisCmdStart;
1970 /* Free the duplicated strings, and restore the context */
1971 if (varidx >=0) {
1972 int i;
1973 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
1974 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
1975 (forloopcontext.variable[i] != nullW)) {
1976 heap_free(forloopcontext.variable[i]);
1981 /* Restore the original for variable contextx */
1982 forloopcontext = oldcontext;
1985 /**************************************************************************
1986 * WCMD_forf_getinputhandle
1988 * Return a file handle which can be used for reading the input lines,
1989 * either to a specific file (which may be quote delimited as we have to
1990 * read the parameters in raw mode) or to a command which we need to
1991 * execute. The command being executed runs in its own shell and stores
1992 * its data in a temporary file.
1994 * Parameters:
1995 * usebackq [I] - Indicates whether usebackq is in effect or not
1996 * itemStr [I] - The item to be handled, either a filename or
1997 * whole command string to execute
1998 * iscmd [I] - Identifies whether this is a command or not
2000 * Returns a file handle which can be used to read the input lines from.
2002 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2003 WCHAR temp_str[MAX_PATH];
2004 WCHAR temp_file[MAX_PATH];
2005 WCHAR temp_cmd[MAXSTRING];
2006 HANDLE hinput = INVALID_HANDLE_VALUE;
2007 static const WCHAR redirOutW[] = {'>','%','s','\0'};
2008 static const WCHAR cmdW[] = {'C','M','D','\0'};
2009 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
2010 '/','C',' ','"','%','s','"','\0'};
2012 /* Remove leading and trailing character */
2013 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2014 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2015 (!iscmd && (itemstr[0] == '"' && usebackq)))
2017 itemstr[strlenW(itemstr)-1] = 0x00;
2018 itemstr++;
2021 if (iscmd) {
2022 /* Get temp filename */
2023 GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
2024 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
2026 /* Redirect output to the temporary file */
2027 wsprintfW(temp_str, redirOutW, temp_file);
2028 wsprintfW(temp_cmd, cmdslashcW, itemstr);
2029 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2030 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2031 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2033 /* Open the file, read line by line and process */
2034 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2035 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2037 } else {
2038 /* Open the file, read line by line and process */
2039 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2040 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2041 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2043 return hinput;
2046 /**************************************************************************
2047 * WCMD_for
2049 * Batch file loop processing.
2051 * On entry: cmdList contains the syntax up to the set
2052 * next cmdList and all in that bracket contain the set data
2053 * next cmdlist contains the DO cmd
2054 * following that is either brackets or && entries (as per if)
2058 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2060 WIN32_FIND_DATAW fd;
2061 HANDLE hff;
2062 int i;
2063 static const WCHAR inW[] = {'i','n'};
2064 static const WCHAR doW[] = {'d','o'};
2065 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2066 WCHAR variable[4];
2067 int varidx = -1;
2068 WCHAR *oldvariablevalue;
2069 WCHAR *firstCmd;
2070 int thisDepth;
2071 WCHAR optionsRoot[MAX_PATH];
2072 DIRECTORY_STACK *dirsToWalk = NULL;
2073 BOOL expandDirs = FALSE;
2074 BOOL useNumbers = FALSE;
2075 BOOL doFileset = FALSE;
2076 BOOL doRecurse = FALSE;
2077 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2078 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2079 int itemNum;
2080 CMD_LIST *thisCmdStart;
2081 int parameterNo = 0;
2082 WCHAR forf_eol = 0;
2083 int forf_skip = 0;
2084 WCHAR forf_delims[256];
2085 WCHAR forf_tokens[MAXSTRING];
2086 BOOL forf_usebackq = FALSE;
2088 /* Handle optional qualifiers (multiple are allowed) */
2089 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2091 optionsRoot[0] = 0;
2092 while (thisArg && *thisArg == '/') {
2093 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2094 thisArg++;
2095 switch (toupperW(*thisArg)) {
2096 case 'D': expandDirs = TRUE; break;
2097 case 'L': useNumbers = TRUE; break;
2099 /* Recursive is special case - /R can have an optional path following it */
2100 /* filenamesets are another special case - /F can have an optional options following it */
2101 case 'R':
2102 case 'F':
2104 /* When recursing directories, use current directory as the starting point unless
2105 subsequently overridden */
2106 doRecurse = (toupperW(*thisArg) == 'R');
2107 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
2109 doFileset = (toupperW(*thisArg) == 'F');
2111 /* Retrieve next parameter to see if is root/options (raw form required
2112 with for /f, or unquoted in for /r) */
2113 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2115 /* Next parm is either qualifier, path/options or variable -
2116 only care about it if it is the path/options */
2117 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2118 parameterNo++;
2119 strcpyW(optionsRoot, thisArg);
2121 break;
2123 default:
2124 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2127 /* Step to next token */
2128 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2131 /* Ensure line continues with variable */
2132 if (!*thisArg || *thisArg != '%') {
2133 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2134 return;
2137 /* With for /f parse the options if provided */
2138 if (doFileset) {
2139 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2140 forf_delims, forf_tokens, &forf_usebackq))
2142 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2143 return;
2146 /* Set up the list of directories to recurse if we are going to */
2147 } else if (doRecurse) {
2148 /* Allocate memory, add to list */
2149 dirsToWalk = heap_alloc(sizeof(DIRECTORY_STACK));
2150 dirsToWalk->next = NULL;
2151 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2152 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2155 /* Variable should follow */
2156 strcpyW(variable, thisArg);
2157 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2158 varidx = FOR_VAR_IDX(variable[1]);
2160 /* Ensure line continues with IN */
2161 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2162 if (!thisArg
2163 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2164 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
2165 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
2166 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2167 return;
2170 /* Save away where the set of data starts and the variable */
2171 thisDepth = (*cmdList)->bracketDepth;
2172 *cmdList = (*cmdList)->nextcommand;
2173 setStart = (*cmdList);
2175 /* Skip until the close bracket */
2176 WINE_TRACE("Searching %p as the set\n", *cmdList);
2177 while (*cmdList &&
2178 (*cmdList)->command != NULL &&
2179 (*cmdList)->bracketDepth > thisDepth) {
2180 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2181 *cmdList = (*cmdList)->nextcommand;
2184 /* Skip the close bracket, if there is one */
2185 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2187 /* Syntax error if missing close bracket, or nothing following it
2188 and once we have the complete set, we expect a DO */
2189 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2190 if ((*cmdList == NULL)
2191 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
2193 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2194 return;
2197 cmdEnd = *cmdList;
2199 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2200 mode, or once for the rest of the time. */
2201 do {
2203 /* Save away the starting position for the commands (and offset for the
2204 first one) */
2205 cmdStart = *cmdList;
2206 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2207 itemNum = 0;
2209 /* If we are recursing directories (ie /R), add all sub directories now, then
2210 prefix the root when searching for the item */
2211 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2213 thisSet = setStart;
2214 /* Loop through all set entries */
2215 while (thisSet &&
2216 thisSet->command != NULL &&
2217 thisSet->bracketDepth >= thisDepth) {
2219 /* Loop through all entries on the same line */
2220 WCHAR *item;
2221 WCHAR *itemStart;
2222 WCHAR buffer[MAXSTRING];
2224 WINE_TRACE("Processing for set %p\n", thisSet);
2225 i = 0;
2226 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2229 * If the parameter within the set has a wildcard then search for matching files
2230 * otherwise do a literal substitution.
2232 static const WCHAR wildcards[] = {'*','?','\0'};
2233 thisCmdStart = cmdStart;
2235 itemNum++;
2236 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2238 if (!useNumbers && !doFileset) {
2239 WCHAR fullitem[MAX_PATH];
2240 int prefixlen = 0;
2242 /* Now build the item to use / search for in the specified directory,
2243 as it is fully qualified in the /R case */
2244 if (dirsToWalk) {
2245 strcpyW(fullitem, dirsToWalk->dirName);
2246 strcatW(fullitem, slashW);
2247 strcatW(fullitem, item);
2248 } else {
2249 WCHAR *prefix = strrchrW(item, '\\');
2250 if (prefix) prefixlen = (prefix - item) + 1;
2251 strcpyW(fullitem, item);
2254 if (strpbrkW (fullitem, wildcards)) {
2255 hff = FindFirstFileW(fullitem, &fd);
2256 if (hff != INVALID_HANDLE_VALUE) {
2257 do {
2258 BOOL isDirectory = FALSE;
2260 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2262 /* Handle as files or dirs appropriately, but ignore . and .. */
2263 if (isDirectory == expandDirs &&
2264 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2265 (strcmpW(fd.cFileName, dotW) != 0))
2267 thisCmdStart = cmdStart;
2268 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2270 if (doRecurse) {
2271 strcpyW(fullitem, dirsToWalk->dirName);
2272 strcatW(fullitem, slashW);
2273 strcatW(fullitem, fd.cFileName);
2274 } else {
2275 if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
2276 fullitem[prefixlen] = 0x00;
2277 strcatW(fullitem, fd.cFileName);
2279 doExecuted = TRUE;
2281 /* Save away any existing for variable context (e.g. nested for loops)
2282 and restore it after executing the body of this for loop */
2283 if (varidx >= 0) {
2284 oldvariablevalue = forloopcontext.variable[varidx];
2285 forloopcontext.variable[varidx] = fullitem;
2287 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2288 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2290 cmdEnd = thisCmdStart;
2292 } while (FindNextFileW(hff, &fd) != 0);
2293 FindClose (hff);
2295 } else {
2296 doExecuted = TRUE;
2298 /* Save away any existing for variable context (e.g. nested for loops)
2299 and restore it after executing the body of this for loop */
2300 if (varidx >= 0) {
2301 oldvariablevalue = forloopcontext.variable[varidx];
2302 forloopcontext.variable[varidx] = fullitem;
2304 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2305 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2307 cmdEnd = thisCmdStart;
2310 } else if (useNumbers) {
2311 /* Convert the first 3 numbers to signed longs and save */
2312 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2313 /* else ignore them! */
2315 /* Filesets - either a list of files, or a command to run and parse the output */
2316 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2317 (forf_usebackq && *itemStart != '\''))) {
2319 HANDLE input;
2320 WCHAR *itemparm;
2322 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2323 wine_dbgstr_w(item));
2325 /* If backquote or single quote, we need to launch that command
2326 and parse the results - use a temporary file */
2327 if ((forf_usebackq && *itemStart == '`') ||
2328 (!forf_usebackq && *itemStart == '\'')) {
2330 /* Use itemstart because the command is the whole set, not just the first token */
2331 itemparm = itemStart;
2332 } else {
2334 /* Use item because the file to process is just the first item in the set */
2335 itemparm = item;
2337 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2339 /* Process the input file */
2340 if (input == INVALID_HANDLE_VALUE) {
2341 WCMD_print_error ();
2342 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2343 errorlevel = 1;
2344 return; /* FOR loop aborts at first failure here */
2346 } else {
2348 /* Read line by line until end of file */
2349 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2350 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2351 &forf_skip, forf_eol, forf_delims, forf_tokens);
2352 buffer[0] = 0;
2354 CloseHandle (input);
2357 /* When we have processed the item as a whole command, abort future set processing */
2358 if (itemparm==itemStart) {
2359 thisSet = NULL;
2360 break;
2363 /* Filesets - A string literal */
2364 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2365 (forf_usebackq && *itemStart == '\''))) {
2367 /* Remove leading and trailing character, ready to parse with delims= delimiters
2368 Note that the last quote is removed from the set and the string terminates
2369 there to mimic windows */
2370 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2371 if (strend) {
2372 *strend = 0x00;
2373 itemStart++;
2376 /* Copy the item away from the global buffer used by WCMD_parameter */
2377 strcpyW(buffer, itemStart);
2378 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2379 &forf_skip, forf_eol, forf_delims, forf_tokens);
2381 /* Only one string can be supplied in the whole set, abort future set processing */
2382 thisSet = NULL;
2383 break;
2386 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2387 i++;
2390 /* Move onto the next set line */
2391 if (thisSet) thisSet = thisSet->nextcommand;
2394 /* If /L is provided, now run the for loop */
2395 if (useNumbers) {
2396 WCHAR thisNum[20];
2397 static const WCHAR fmt[] = {'%','d','\0'};
2399 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2400 numbers[0], numbers[2], numbers[1]);
2401 for (i=numbers[0];
2402 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2403 i=i + numbers[1]) {
2405 sprintfW(thisNum, fmt, i);
2406 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2408 thisCmdStart = cmdStart;
2409 doExecuted = TRUE;
2411 /* Save away any existing for variable context (e.g. nested for loops)
2412 and restore it after executing the body of this for loop */
2413 if (varidx >= 0) {
2414 oldvariablevalue = forloopcontext.variable[varidx];
2415 forloopcontext.variable[varidx] = thisNum;
2417 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2418 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2420 cmdEnd = thisCmdStart;
2423 /* If we are walking directories, move on to any which remain */
2424 if (dirsToWalk != NULL) {
2425 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2426 heap_free(dirsToWalk->dirName);
2427 heap_free(dirsToWalk);
2428 dirsToWalk = nextDir;
2429 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2430 wine_dbgstr_w(dirsToWalk->dirName));
2431 else WINE_TRACE("Finished all directories.\n");
2434 } while (dirsToWalk != NULL);
2436 /* Now skip over the do part if we did not perform the for loop so far.
2437 We store in cmdEnd the next command after the do block, but we only
2438 know this if something was run. If it has not been, we need to calculate
2439 it. */
2440 if (!doExecuted) {
2441 thisCmdStart = cmdStart;
2442 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2443 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2444 cmdEnd = thisCmdStart;
2447 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2448 all processing, OR it should be pointing to the end of && processing OR
2449 it should be pointing at the NULL end of bracket for the DO. The return
2450 value needs to be the NEXT command to execute, which it either is, or
2451 we need to step over the closing bracket */
2452 *cmdList = cmdEnd;
2453 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2456 /**************************************************************************
2457 * WCMD_give_help
2459 * Simple on-line help. Help text is stored in the resource file.
2462 void WCMD_give_help (const WCHAR *args)
2464 size_t i;
2466 args = WCMD_skip_leading_spaces((WCHAR*) args);
2467 if (strlenW(args) == 0) {
2468 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2470 else {
2471 /* Display help message for builtin commands */
2472 for (i=0; i<=WCMD_EXIT; i++) {
2473 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2474 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2475 WCMD_output_asis (WCMD_LoadMessage(i));
2476 return;
2479 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2480 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2481 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2482 args, -1, externals[i], -1) == CSTR_EQUAL) {
2483 WCHAR cmd[128];
2484 static const WCHAR helpW[] = {' ', '/','?','\0'};
2485 strcpyW(cmd, args);
2486 strcatW(cmd, helpW);
2487 WCMD_run_program(cmd, FALSE);
2488 return;
2491 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2493 return;
2496 /****************************************************************************
2497 * WCMD_go_to
2499 * Batch file jump instruction. Not the most efficient algorithm ;-)
2500 * Prints error message if the specified label cannot be found - the file pointer is
2501 * then at EOF, effectively stopping the batch file.
2502 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2505 void WCMD_goto (CMD_LIST **cmdList) {
2507 WCHAR string[MAX_PATH];
2508 WCHAR *labelend = NULL;
2509 const WCHAR labelEndsW[] = {'>','<','|','&',' ',':','\t','\0'};
2511 /* Do not process any more parts of a processed multipart or multilines command */
2512 if (cmdList) *cmdList = NULL;
2514 if (context != NULL) {
2515 WCHAR *paramStart = param1, *str;
2516 static const WCHAR eofW[] = {':','e','o','f','\0'};
2518 if (param1[0] == 0x00) {
2519 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2520 return;
2523 /* Handle special :EOF label */
2524 if (lstrcmpiW (eofW, param1) == 0) {
2525 context -> skip_rest = TRUE;
2526 return;
2529 /* Support goto :label as well as goto label plus remove trailing chars */
2530 if (*paramStart == ':') paramStart++;
2531 labelend = strpbrkW(paramStart, labelEndsW);
2532 if (labelend) *labelend = 0x00;
2533 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
2535 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2536 while (*paramStart &&
2537 WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2538 str = string;
2540 /* Ignore leading whitespace or no-echo character */
2541 while (*str=='@' || isspaceW (*str)) str++;
2543 /* If the first real character is a : then this is a label */
2544 if (*str == ':') {
2545 str++;
2547 /* Skip spaces between : and label */
2548 while (isspaceW (*str)) str++;
2549 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
2551 /* Label ends at whitespace or redirection characters */
2552 labelend = strpbrkW(str, labelEndsW);
2553 if (labelend) *labelend = 0x00;
2554 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
2556 if (lstrcmpiW (str, paramStart) == 0) return;
2559 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2560 context -> skip_rest = TRUE;
2562 return;
2565 /*****************************************************************************
2566 * WCMD_pushd
2568 * Push a directory onto the stack
2571 void WCMD_pushd (const WCHAR *args)
2573 struct env_stack *curdir;
2574 WCHAR *thisdir;
2575 static const WCHAR parmD[] = {'/','D','\0'};
2577 if (strchrW(args, '/') != NULL) {
2578 SetLastError(ERROR_INVALID_PARAMETER);
2579 WCMD_print_error();
2580 return;
2583 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2584 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2585 if( !curdir || !thisdir ) {
2586 LocalFree(curdir);
2587 LocalFree(thisdir);
2588 WINE_ERR ("out of memory\n");
2589 return;
2592 /* Change directory using CD code with /D parameter */
2593 strcpyW(quals, parmD);
2594 GetCurrentDirectoryW (1024, thisdir);
2595 errorlevel = 0;
2596 WCMD_setshow_default(args);
2597 if (errorlevel) {
2598 LocalFree(curdir);
2599 LocalFree(thisdir);
2600 return;
2601 } else {
2602 curdir -> next = pushd_directories;
2603 curdir -> strings = thisdir;
2604 if (pushd_directories == NULL) {
2605 curdir -> u.stackdepth = 1;
2606 } else {
2607 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2609 pushd_directories = curdir;
2614 /*****************************************************************************
2615 * WCMD_popd
2617 * Pop a directory from the stack
2620 void WCMD_popd (void) {
2621 struct env_stack *temp = pushd_directories;
2623 if (!pushd_directories)
2624 return;
2626 /* pop the old environment from the stack, and make it the current dir */
2627 pushd_directories = temp->next;
2628 SetCurrentDirectoryW(temp->strings);
2629 LocalFree (temp->strings);
2630 LocalFree (temp);
2633 /*******************************************************************
2634 * evaluate_if_comparison
2636 * Evaluates an "if" comparison operation
2638 * PARAMS
2639 * leftOperand [I] left operand, non NULL
2640 * operator [I] "if" binary comparison operator, non NULL
2641 * rightOperand [I] right operand, non NULL
2642 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2644 * RETURNS
2645 * Success: 1 if operator applied to the operands evaluates to TRUE
2646 * 0 if operator applied to the operands evaluates to FALSE
2647 * Failure: -1 if operator is not recognized
2649 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2650 const WCHAR *rightOperand, int caseInsensitive)
2652 WCHAR *endptr_leftOp, *endptr_rightOp;
2653 long int leftOperand_int, rightOperand_int;
2654 BOOL int_operands;
2655 static const WCHAR lssW[] = {'l','s','s','\0'};
2656 static const WCHAR leqW[] = {'l','e','q','\0'};
2657 static const WCHAR equW[] = {'e','q','u','\0'};
2658 static const WCHAR neqW[] = {'n','e','q','\0'};
2659 static const WCHAR geqW[] = {'g','e','q','\0'};
2660 static const WCHAR gtrW[] = {'g','t','r','\0'};
2662 /* == is a special case, as it always compares strings */
2663 if (!lstrcmpiW(operator, eqeqW))
2664 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2665 : lstrcmpW (leftOperand, rightOperand) == 0;
2667 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2668 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2669 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2670 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2672 /* Perform actual (integer or string) comparison */
2673 if (!lstrcmpiW(operator, lssW)) {
2674 if (int_operands)
2675 return leftOperand_int < rightOperand_int;
2676 else
2677 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2678 : lstrcmpW (leftOperand, rightOperand) < 0;
2681 if (!lstrcmpiW(operator, leqW)) {
2682 if (int_operands)
2683 return leftOperand_int <= rightOperand_int;
2684 else
2685 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2686 : lstrcmpW (leftOperand, rightOperand) <= 0;
2689 if (!lstrcmpiW(operator, equW)) {
2690 if (int_operands)
2691 return leftOperand_int == rightOperand_int;
2692 else
2693 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2694 : lstrcmpW (leftOperand, rightOperand) == 0;
2697 if (!lstrcmpiW(operator, neqW)) {
2698 if (int_operands)
2699 return leftOperand_int != rightOperand_int;
2700 else
2701 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2702 : lstrcmpW (leftOperand, rightOperand) != 0;
2705 if (!lstrcmpiW(operator, geqW)) {
2706 if (int_operands)
2707 return leftOperand_int >= rightOperand_int;
2708 else
2709 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2710 : lstrcmpW (leftOperand, rightOperand) >= 0;
2713 if (!lstrcmpiW(operator, gtrW)) {
2714 if (int_operands)
2715 return leftOperand_int > rightOperand_int;
2716 else
2717 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2718 : lstrcmpW (leftOperand, rightOperand) > 0;
2721 return -1;
2724 /****************************************************************************
2725 * WCMD_if
2727 * Batch file conditional.
2729 * On entry, cmdlist will point to command containing the IF, and optionally
2730 * the first command to execute (if brackets not found)
2731 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2732 * If ('s were found, execute all within that bracket
2733 * Command may optionally be followed by an ELSE - need to skip instructions
2734 * in the else using the same logic
2736 * FIXME: Much more syntax checking needed!
2738 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2740 int negate; /* Negate condition */
2741 int test; /* Condition evaluation result */
2742 WCHAR condition[MAX_PATH], *command;
2743 static const WCHAR notW[] = {'n','o','t','\0'};
2744 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2745 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2746 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2747 static const WCHAR parmI[] = {'/','I','\0'};
2748 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2750 negate = !lstrcmpiW(param1,notW);
2751 strcpyW(condition, (negate ? param2 : param1));
2752 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2754 if (!lstrcmpiW (condition, errlvlW)) {
2755 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2756 WCHAR *endptr;
2757 long int param_int = strtolW(param, &endptr, 10);
2758 if (*endptr) goto syntax_err;
2759 test = ((long int)errorlevel >= param_int);
2760 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2762 else if (!lstrcmpiW (condition, existW)) {
2763 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2764 != INVALID_FILE_ATTRIBUTES);
2765 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2767 else if (!lstrcmpiW (condition, defdW)) {
2768 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2769 NULL, 0) > 0);
2770 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2772 else { /* comparison operation */
2773 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2774 WCHAR *paramStart;
2776 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, &paramStart, TRUE, FALSE));
2777 if (!*leftOperand)
2778 goto syntax_err;
2780 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2781 p = paramStart + strlenW(leftOperand);
2782 while (*p == ' ' || *p == '\t')
2783 p++;
2785 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2786 strcpyW(operator, eqeqW);
2787 else {
2788 strcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2789 if (!*operator) goto syntax_err;
2791 p += strlenW(operator);
2793 strcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2794 if (!*rightOperand)
2795 goto syntax_err;
2797 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2798 if (test == -1)
2799 goto syntax_err;
2801 p = paramStart + strlenW(rightOperand);
2802 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2805 /* Process rest of IF statement which is on the same line
2806 Note: This may process all or some of the cmdList (eg a GOTO) */
2807 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2808 return;
2810 syntax_err:
2811 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2814 /****************************************************************************
2815 * WCMD_move
2817 * Move a file, directory tree or wildcarded set of files.
2820 void WCMD_move (void)
2822 BOOL status;
2823 WIN32_FIND_DATAW fd;
2824 HANDLE hff;
2825 WCHAR input[MAX_PATH];
2826 WCHAR output[MAX_PATH];
2827 WCHAR drive[10];
2828 WCHAR dir[MAX_PATH];
2829 WCHAR fname[MAX_PATH];
2830 WCHAR ext[MAX_PATH];
2832 if (param1[0] == 0x00) {
2833 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2834 return;
2837 /* If no destination supplied, assume current directory */
2838 if (param2[0] == 0x00) {
2839 strcpyW(param2, dotW);
2842 /* If 2nd parm is directory, then use original filename */
2843 /* Convert partial path to full path */
2844 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2845 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2846 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2847 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2849 /* Split into components */
2850 WCMD_splitpath(input, drive, dir, fname, ext);
2852 hff = FindFirstFileW(input, &fd);
2853 if (hff == INVALID_HANDLE_VALUE)
2854 return;
2856 do {
2857 WCHAR dest[MAX_PATH];
2858 WCHAR src[MAX_PATH];
2859 DWORD attribs;
2860 BOOL ok = TRUE;
2862 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2864 /* Build src & dest name */
2865 strcpyW(src, drive);
2866 strcatW(src, dir);
2868 /* See if dest is an existing directory */
2869 attribs = GetFileAttributesW(output);
2870 if (attribs != INVALID_FILE_ATTRIBUTES &&
2871 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2872 strcpyW(dest, output);
2873 strcatW(dest, slashW);
2874 strcatW(dest, fd.cFileName);
2875 } else {
2876 strcpyW(dest, output);
2879 strcatW(src, fd.cFileName);
2881 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2882 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2884 /* If destination exists, prompt unless /Y supplied */
2885 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2886 BOOL force = FALSE;
2887 WCHAR copycmd[MAXSTRING];
2888 DWORD len;
2890 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2891 if (strstrW (quals, parmNoY))
2892 force = FALSE;
2893 else if (strstrW (quals, parmY))
2894 force = TRUE;
2895 else {
2896 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2897 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2898 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2899 && ! lstrcmpiW (copycmd, parmY));
2902 /* Prompt if overwriting */
2903 if (!force) {
2904 WCHAR* question;
2906 /* Ask for confirmation */
2907 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2908 ok = WCMD_ask_confirm(question, FALSE, NULL);
2909 LocalFree(question);
2911 /* So delete the destination prior to the move */
2912 if (ok) {
2913 if (!DeleteFileW(dest)) {
2914 WCMD_print_error ();
2915 errorlevel = 1;
2916 ok = FALSE;
2922 if (ok) {
2923 status = MoveFileW(src, dest);
2924 } else {
2925 status = TRUE;
2928 if (!status) {
2929 WCMD_print_error ();
2930 errorlevel = 1;
2932 } while (FindNextFileW(hff, &fd) != 0);
2934 FindClose(hff);
2937 /****************************************************************************
2938 * WCMD_pause
2940 * Suspend execution of a batch script until a key is typed
2943 void WCMD_pause (void)
2945 DWORD oldmode;
2946 BOOL have_console;
2947 DWORD count;
2948 WCHAR key;
2949 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2951 have_console = GetConsoleMode(hIn, &oldmode);
2952 if (have_console)
2953 SetConsoleMode(hIn, 0);
2955 WCMD_output_asis(anykey);
2956 WCMD_ReadFile(hIn, &key, 1, &count);
2957 if (have_console)
2958 SetConsoleMode(hIn, oldmode);
2961 /****************************************************************************
2962 * WCMD_remove_dir
2964 * Delete a directory.
2967 void WCMD_remove_dir (WCHAR *args) {
2969 int argno = 0;
2970 int argsProcessed = 0;
2971 WCHAR *argN = args;
2972 static const WCHAR parmS[] = {'/','S','\0'};
2973 static const WCHAR parmQ[] = {'/','Q','\0'};
2975 /* Loop through all args */
2976 while (argN) {
2977 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2978 if (argN && argN[0] != '/') {
2979 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2980 wine_dbgstr_w(quals));
2981 argsProcessed++;
2983 /* If subdirectory search not supplied, just try to remove
2984 and report error if it fails (eg if it contains a file) */
2985 if (strstrW (quals, parmS) == NULL) {
2986 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2988 /* Otherwise use ShFileOp to recursively remove a directory */
2989 } else {
2991 SHFILEOPSTRUCTW lpDir;
2993 /* Ask first */
2994 if (strstrW (quals, parmQ) == NULL) {
2995 BOOL ok;
2996 WCHAR question[MAXSTRING];
2997 static const WCHAR fmt[] = {'%','s',' ','\0'};
2999 /* Ask for confirmation */
3000 wsprintfW(question, fmt, thisArg);
3001 ok = WCMD_ask_confirm(question, TRUE, NULL);
3003 /* Abort if answer is 'N' */
3004 if (!ok) return;
3007 /* Do the delete */
3008 lpDir.hwnd = NULL;
3009 lpDir.pTo = NULL;
3010 lpDir.pFrom = thisArg;
3011 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
3012 lpDir.wFunc = FO_DELETE;
3014 /* SHFileOperationW needs file list with a double null termination */
3015 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3017 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3022 /* Handle no valid args */
3023 if (argsProcessed == 0) {
3024 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3025 return;
3030 /****************************************************************************
3031 * WCMD_rename
3033 * Rename a file.
3036 void WCMD_rename (void)
3038 BOOL status;
3039 HANDLE hff;
3040 WIN32_FIND_DATAW fd;
3041 WCHAR input[MAX_PATH];
3042 WCHAR *dotDst = NULL;
3043 WCHAR drive[10];
3044 WCHAR dir[MAX_PATH];
3045 WCHAR fname[MAX_PATH];
3046 WCHAR ext[MAX_PATH];
3048 errorlevel = 0;
3050 /* Must be at least two args */
3051 if (param1[0] == 0x00 || param2[0] == 0x00) {
3052 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3053 errorlevel = 1;
3054 return;
3057 /* Destination cannot contain a drive letter or directory separator */
3058 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
3059 SetLastError(ERROR_INVALID_PARAMETER);
3060 WCMD_print_error();
3061 errorlevel = 1;
3062 return;
3065 /* Convert partial path to full path */
3066 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
3067 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3068 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3069 dotDst = strchrW(param2, '.');
3071 /* Split into components */
3072 WCMD_splitpath(input, drive, dir, fname, ext);
3074 hff = FindFirstFileW(input, &fd);
3075 if (hff == INVALID_HANDLE_VALUE)
3076 return;
3078 do {
3079 WCHAR dest[MAX_PATH];
3080 WCHAR src[MAX_PATH];
3081 WCHAR *dotSrc = NULL;
3082 int dirLen;
3084 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3086 /* FIXME: If dest name or extension is *, replace with filename/ext
3087 part otherwise use supplied name. This supports:
3088 ren *.fred *.jim
3089 ren jim.* fred.* etc
3090 However, windows has a more complex algorithm supporting eg
3091 ?'s and *'s mid name */
3092 dotSrc = strchrW(fd.cFileName, '.');
3094 /* Build src & dest name */
3095 strcpyW(src, drive);
3096 strcatW(src, dir);
3097 strcpyW(dest, src);
3098 dirLen = strlenW(src);
3099 strcatW(src, fd.cFileName);
3101 /* Build name */
3102 if (param2[0] == '*') {
3103 strcatW(dest, fd.cFileName);
3104 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3105 } else {
3106 strcatW(dest, param2);
3107 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3110 /* Build Extension */
3111 if (dotDst && (*(dotDst+1)=='*')) {
3112 if (dotSrc) strcatW(dest, dotSrc);
3113 } else if (dotDst) {
3114 strcatW(dest, dotDst);
3117 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3118 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3120 status = MoveFileW(src, dest);
3122 if (!status) {
3123 WCMD_print_error ();
3124 errorlevel = 1;
3126 } while (FindNextFileW(hff, &fd) != 0);
3128 FindClose(hff);
3131 /*****************************************************************************
3132 * WCMD_dupenv
3134 * Make a copy of the environment.
3136 static WCHAR *WCMD_dupenv( const WCHAR *env )
3138 WCHAR *env_copy;
3139 int len;
3141 if( !env )
3142 return NULL;
3144 len = 0;
3145 while ( env[len] )
3146 len += (strlenW(&env[len]) + 1);
3148 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3149 if (!env_copy)
3151 WINE_ERR("out of memory\n");
3152 return env_copy;
3154 memcpy (env_copy, env, len*sizeof (WCHAR));
3155 env_copy[len] = 0;
3157 return env_copy;
3160 /*****************************************************************************
3161 * WCMD_setlocal
3163 * setlocal pushes the environment onto a stack
3164 * Save the environment as unicode so we don't screw anything up.
3166 void WCMD_setlocal (const WCHAR *s) {
3167 WCHAR *env;
3168 struct env_stack *env_copy;
3169 WCHAR cwd[MAX_PATH];
3170 BOOL newdelay;
3171 static const WCHAR ondelayW[] = {'E','N','A','B','L','E','D','E','L','A',
3172 'Y','E','D','E','X','P','A','N','S','I',
3173 'O','N','\0'};
3174 static const WCHAR offdelayW[] = {'D','I','S','A','B','L','E','D','E','L',
3175 'A','Y','E','D','E','X','P','A','N','S',
3176 'I','O','N','\0'};
3178 /* setlocal does nothing outside of batch programs */
3179 if (!context) return;
3181 /* DISABLEEXTENSIONS ignored */
3183 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3184 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3185 if (!strcmpiW(param1, ondelayW) || !strcmpiW(param2, ondelayW)) {
3186 newdelay = TRUE;
3187 } else if (!strcmpiW(param1, offdelayW) || !strcmpiW(param2, offdelayW)) {
3188 newdelay = FALSE;
3189 } else {
3190 newdelay = delayedsubst;
3192 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3194 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3195 if( !env_copy )
3197 WINE_ERR ("out of memory\n");
3198 return;
3201 env = GetEnvironmentStringsW ();
3202 env_copy->strings = WCMD_dupenv (env);
3203 if (env_copy->strings)
3205 env_copy->batchhandle = context->h;
3206 env_copy->next = saved_environment;
3207 env_copy->delayedsubst = delayedsubst;
3208 delayedsubst = newdelay;
3209 saved_environment = env_copy;
3211 /* Save the current drive letter */
3212 GetCurrentDirectoryW(MAX_PATH, cwd);
3213 env_copy->u.cwd = cwd[0];
3215 else
3216 LocalFree (env_copy);
3218 FreeEnvironmentStringsW (env);
3222 /*****************************************************************************
3223 * WCMD_endlocal
3225 * endlocal pops the environment off a stack
3226 * Note: When searching for '=', search from WCHAR position 1, to handle
3227 * special internal environment variables =C:, =D: etc
3229 void WCMD_endlocal (void) {
3230 WCHAR *env, *old, *p;
3231 struct env_stack *temp;
3232 int len, n;
3234 /* setlocal does nothing outside of batch programs */
3235 if (!context) return;
3237 /* setlocal needs a saved environment from within the same context (batch
3238 program) as it was saved in */
3239 if (!saved_environment || saved_environment->batchhandle != context->h)
3240 return;
3242 /* pop the old environment from the stack */
3243 temp = saved_environment;
3244 saved_environment = temp->next;
3246 /* delete the current environment, totally */
3247 env = GetEnvironmentStringsW ();
3248 old = WCMD_dupenv (env);
3249 len = 0;
3250 while (old[len]) {
3251 n = strlenW(&old[len]) + 1;
3252 p = strchrW(&old[len] + 1, '=');
3253 if (p)
3255 *p++ = 0;
3256 SetEnvironmentVariableW (&old[len], NULL);
3258 len += n;
3260 LocalFree (old);
3261 FreeEnvironmentStringsW (env);
3263 /* restore old environment */
3264 env = temp->strings;
3265 len = 0;
3266 delayedsubst = temp->delayedsubst;
3267 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3268 while (env[len]) {
3269 n = strlenW(&env[len]) + 1;
3270 p = strchrW(&env[len] + 1, '=');
3271 if (p)
3273 *p++ = 0;
3274 SetEnvironmentVariableW (&env[len], p);
3276 len += n;
3279 /* Restore current drive letter */
3280 if (IsCharAlphaW(temp->u.cwd)) {
3281 WCHAR envvar[4];
3282 WCHAR cwd[MAX_PATH];
3283 static const WCHAR fmt[] = {'=','%','c',':','\0'};
3285 wsprintfW(envvar, fmt, temp->u.cwd);
3286 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3287 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3288 SetCurrentDirectoryW(cwd);
3292 LocalFree (env);
3293 LocalFree (temp);
3296 /*****************************************************************************
3297 * WCMD_setshow_default
3299 * Set/Show the current default directory
3302 void WCMD_setshow_default (const WCHAR *args) {
3304 BOOL status;
3305 WCHAR string[1024];
3306 WCHAR cwd[1024];
3307 WCHAR *pos;
3308 WIN32_FIND_DATAW fd;
3309 HANDLE hff;
3310 static const WCHAR parmD[] = {'/','D','\0'};
3312 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3314 /* Skip /D and trailing whitespace if on the front of the command line */
3315 if (strlenW(args) >= 2 &&
3316 CompareStringW(LOCALE_USER_DEFAULT,
3317 NORM_IGNORECASE | SORT_STRINGSORT,
3318 args, 2, parmD, -1) == CSTR_EQUAL) {
3319 args += 2;
3320 while (*args && (*args==' ' || *args=='\t'))
3321 args++;
3324 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3325 if (strlenW(args) == 0) {
3326 strcatW (cwd, newlineW);
3327 WCMD_output_asis (cwd);
3329 else {
3330 /* Remove any double quotes, which may be in the
3331 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3332 pos = string;
3333 while (*args) {
3334 if (*args != '"') *pos++ = *args;
3335 args++;
3337 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3338 pos--;
3339 *pos = 0x00;
3341 /* Search for appropriate directory */
3342 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3343 hff = FindFirstFileW(string, &fd);
3344 if (hff != INVALID_HANDLE_VALUE) {
3345 do {
3346 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3347 WCHAR fpath[MAX_PATH];
3348 WCHAR drive[10];
3349 WCHAR dir[MAX_PATH];
3350 WCHAR fname[MAX_PATH];
3351 WCHAR ext[MAX_PATH];
3352 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3354 /* Convert path into actual directory spec */
3355 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3356 WCMD_splitpath(fpath, drive, dir, fname, ext);
3358 /* Rebuild path */
3359 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3360 break;
3362 } while (FindNextFileW(hff, &fd) != 0);
3363 FindClose(hff);
3366 /* Change to that directory */
3367 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3369 status = SetCurrentDirectoryW(string);
3370 if (!status) {
3371 errorlevel = 1;
3372 WCMD_print_error ();
3373 return;
3374 } else {
3376 /* Save away the actual new directory, to store as current location */
3377 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3379 /* Restore old directory if drive letter would change, and
3380 CD x:\directory /D (or pushd c:\directory) not supplied */
3381 if ((strstrW(quals, parmD) == NULL) &&
3382 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3383 SetCurrentDirectoryW(cwd);
3387 /* Set special =C: type environment variable, for drive letter of
3388 change of directory, even if path was restored due to missing
3389 /D (allows changing drive letter when not resident on that
3390 drive */
3391 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3392 WCHAR env[4];
3393 strcpyW(env, equalW);
3394 memcpy(env+1, string, 2 * sizeof(WCHAR));
3395 env[3] = 0x00;
3396 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3397 SetEnvironmentVariableW(env, string);
3401 return;
3404 /****************************************************************************
3405 * WCMD_setshow_date
3407 * Set/Show the system date
3408 * FIXME: Can't change date yet
3411 void WCMD_setshow_date (void) {
3413 WCHAR curdate[64], buffer[64];
3414 DWORD count;
3415 static const WCHAR parmT[] = {'/','T','\0'};
3417 if (strlenW(param1) == 0) {
3418 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3419 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3420 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3421 if (strstrW (quals, parmT) == NULL) {
3422 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3423 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3424 if (count > 2) {
3425 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3429 else WCMD_print_error ();
3431 else {
3432 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3436 /****************************************************************************
3437 * WCMD_compare
3438 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3439 * the equals sign.
3441 static int WCMD_compare( const void *a, const void *b )
3443 int r;
3444 const WCHAR * const *str_a = a, * const *str_b = b;
3445 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3446 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3447 if( r == CSTR_LESS_THAN ) return -1;
3448 if( r == CSTR_GREATER_THAN ) return 1;
3449 return 0;
3452 /****************************************************************************
3453 * WCMD_setshow_sortenv
3455 * sort variables into order for display
3456 * Optionally only display those who start with a stub
3457 * returns the count displayed
3459 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3461 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3462 const WCHAR **str;
3464 if (stub) stublen = strlenW(stub);
3466 /* count the number of strings, and the total length */
3467 while ( s[len] ) {
3468 len += (strlenW(&s[len]) + 1);
3469 count++;
3472 /* add the strings to an array */
3473 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3474 if( !str )
3475 return 0;
3476 str[0] = s;
3477 for( i=1; i<count; i++ )
3478 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3480 /* sort the array */
3481 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3483 /* print it */
3484 for( i=0; i<count; i++ ) {
3485 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3486 NORM_IGNORECASE | SORT_STRINGSORT,
3487 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3488 /* Don't display special internal variables */
3489 if (str[i][0] != '=') {
3490 WCMD_output_asis(str[i]);
3491 WCMD_output_asis(newlineW);
3492 displayedcount++;
3497 LocalFree( str );
3498 return displayedcount;
3501 /****************************************************************************
3502 * WCMD_getprecedence
3503 * Return the precedence of a particular operator
3505 static int WCMD_getprecedence(const WCHAR in)
3507 switch (in) {
3508 case '!':
3509 case '~':
3510 case OP_POSITIVE:
3511 case OP_NEGATIVE:
3512 return 8;
3513 case '*':
3514 case '/':
3515 case '%':
3516 return 7;
3517 case '+':
3518 case '-':
3519 return 6;
3520 case '<':
3521 case '>':
3522 return 5;
3523 case '&':
3524 return 4;
3525 case '^':
3526 return 3;
3527 case '|':
3528 return 2;
3529 case '=':
3530 case OP_ASSSIGNMUL:
3531 case OP_ASSSIGNDIV:
3532 case OP_ASSSIGNMOD:
3533 case OP_ASSSIGNADD:
3534 case OP_ASSSIGNSUB:
3535 case OP_ASSSIGNAND:
3536 case OP_ASSSIGNNOT:
3537 case OP_ASSSIGNOR:
3538 case OP_ASSSIGNSHL:
3539 case OP_ASSSIGNSHR:
3540 return 1;
3541 default:
3542 return 0;
3546 /****************************************************************************
3547 * WCMD_pushnumber
3548 * Push either a number or name (environment variable) onto the supplied
3549 * stack
3551 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3552 VARSTACK *thisstack = heap_alloc(sizeof(VARSTACK));
3553 thisstack->isnum = (var == NULL);
3554 if (var) {
3555 thisstack->variable = var;
3556 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3557 } else {
3558 thisstack->value = num;
3559 WINE_TRACE("Pushed number %d\n", num);
3561 thisstack->next = *varstack;
3562 *varstack = thisstack;
3565 /****************************************************************************
3566 * WCMD_peeknumber
3567 * Returns the value of the top number or environment variable on the stack
3568 * and leaves the item on the stack.
3570 static int WCMD_peeknumber(VARSTACK **varstack) {
3571 int result = 0;
3572 VARSTACK *thisvar;
3574 if (varstack) {
3575 thisvar = *varstack;
3576 if (!thisvar->isnum) {
3577 WCHAR tmpstr[MAXSTRING];
3578 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3579 result = strtoulW(tmpstr,NULL,0);
3581 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3582 } else {
3583 result = thisvar->value;
3586 WINE_TRACE("Peeked number %d\n", result);
3587 return result;
3590 /****************************************************************************
3591 * WCMD_popnumber
3592 * Returns the value of the top number or environment variable on the stack
3593 * and removes the item from the stack.
3595 static int WCMD_popnumber(VARSTACK **varstack) {
3596 int result = 0;
3597 VARSTACK *thisvar;
3599 if (varstack) {
3600 thisvar = *varstack;
3601 result = WCMD_peeknumber(varstack);
3602 if (!thisvar->isnum) heap_free(thisvar->variable);
3603 *varstack = thisvar->next;
3604 heap_free(thisvar);
3606 WINE_TRACE("Popped number %d\n", result);
3607 return result;
3610 /****************************************************************************
3611 * WCMD_pushoperator
3612 * Push an operator onto the supplied stack
3614 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3615 OPSTACK *thisstack = heap_alloc(sizeof(OPSTACK));
3616 thisstack->precedence = precedence;
3617 thisstack->op = op;
3618 thisstack->next = *opstack;
3619 WINE_TRACE("Pushed operator %c\n", op);
3620 *opstack = thisstack;
3623 /****************************************************************************
3624 * WCMD_popoperator
3625 * Returns the operator from the top of the stack and removes the item from
3626 * the stack.
3628 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3629 WCHAR result = 0;
3630 OPSTACK *thisop;
3632 if (opstack) {
3633 thisop = *opstack;
3634 result = thisop->op;
3635 *opstack = thisop->next;
3636 heap_free(thisop);
3638 WINE_TRACE("Popped operator %c\n", result);
3639 return result;
3642 /****************************************************************************
3643 * WCMD_reduce
3644 * Actions the top operator on the stack against the first and sometimes
3645 * second value on the variable stack, and pushes the result
3646 * Returns non-zero on error.
3648 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3649 OPSTACK *thisop;
3650 int var1,var2;
3651 int rc = 0;
3653 if (!*opstack || !*varstack) {
3654 WINE_TRACE("No operators for the reduce\n");
3655 return WCMD_NOOPERATOR;
3658 /* Remove the top operator */
3659 thisop = *opstack;
3660 *opstack = (*opstack)->next;
3661 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop->op);
3663 /* One variable operators */
3664 var1 = WCMD_popnumber(varstack);
3665 switch (thisop->op) {
3666 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3667 break;
3668 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3669 break;
3670 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3671 break;
3672 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3673 break;
3676 /* Two variable operators */
3677 if (!*varstack) {
3678 WINE_TRACE("No operands left for the reduce?\n");
3679 return WCMD_NOOPERAND;
3681 switch (thisop->op) {
3682 case '!':
3683 case '~':
3684 case OP_POSITIVE:
3685 case OP_NEGATIVE:
3686 break; /* Handled above */
3687 case '*': var2 = WCMD_popnumber(varstack);
3688 WCMD_pushnumber(NULL, var2*var1, varstack);
3689 break;
3690 case '/': var2 = WCMD_popnumber(varstack);
3691 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3692 WCMD_pushnumber(NULL, var2/var1, varstack);
3693 break;
3694 case '+': var2 = WCMD_popnumber(varstack);
3695 WCMD_pushnumber(NULL, var2+var1, varstack);
3696 break;
3697 case '-': var2 = WCMD_popnumber(varstack);
3698 WCMD_pushnumber(NULL, var2-var1, varstack);
3699 break;
3700 case '&': var2 = WCMD_popnumber(varstack);
3701 WCMD_pushnumber(NULL, var2&var1, varstack);
3702 break;
3703 case '%': var2 = WCMD_popnumber(varstack);
3704 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3705 WCMD_pushnumber(NULL, var2%var1, varstack);
3706 break;
3707 case '^': var2 = WCMD_popnumber(varstack);
3708 WCMD_pushnumber(NULL, var2^var1, varstack);
3709 break;
3710 case '<': var2 = WCMD_popnumber(varstack);
3711 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3712 which differs from the compiler (for example gcc) so being explicit. */
3713 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3714 WCMD_pushnumber(NULL, 0, varstack);
3715 } else {
3716 WCMD_pushnumber(NULL, var2<<var1, varstack);
3718 break;
3719 case '>': var2 = WCMD_popnumber(varstack);
3720 WCMD_pushnumber(NULL, var2>>var1, varstack);
3721 break;
3722 case '|': var2 = WCMD_popnumber(varstack);
3723 WCMD_pushnumber(NULL, var2|var1, varstack);
3724 break;
3726 case OP_ASSSIGNMUL:
3727 case OP_ASSSIGNDIV:
3728 case OP_ASSSIGNMOD:
3729 case OP_ASSSIGNADD:
3730 case OP_ASSSIGNSUB:
3731 case OP_ASSSIGNAND:
3732 case OP_ASSSIGNNOT:
3733 case OP_ASSSIGNOR:
3734 case OP_ASSSIGNSHL:
3735 case OP_ASSSIGNSHR:
3737 int i = 0;
3739 /* The left of an equals must be one variable */
3740 if (!(*varstack) || (*varstack)->isnum) {
3741 return WCMD_NOOPERAND;
3744 /* Make the number stack grow by inserting the value of the variable */
3745 var2 = WCMD_peeknumber(varstack);
3746 WCMD_pushnumber(NULL, var2, varstack);
3747 WCMD_pushnumber(NULL, var1, varstack);
3749 /* Make the operand stack grow by pushing the assign operator plus the
3750 operator to perform */
3751 while (calcassignments[i].op != ' ' &&
3752 calcassignments[i].calculatedop != thisop->op) {
3753 i++;
3755 if (calcassignments[i].calculatedop == ' ') {
3756 WINE_ERR("Unexpected operator %c\n", thisop->op);
3757 return WCMD_NOOPERATOR;
3759 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3760 WCMD_pushoperator(calcassignments[i].op,
3761 WCMD_getprecedence(calcassignments[i].op), opstack);
3762 break;
3765 case '=':
3767 WCHAR intFormat[] = {'%','d','\0'};
3768 WCHAR result[MAXSTRING];
3770 /* Build the result, then push it onto the stack */
3771 sprintfW(result, intFormat, var1);
3772 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3773 wine_dbgstr_w(result));
3774 SetEnvironmentVariableW((*varstack)->variable, result);
3775 var2 = WCMD_popnumber(varstack);
3776 WCMD_pushnumber(NULL, var1, varstack);
3777 break;
3780 default: WINE_ERR("Unrecognized operator %c\n", thisop->op);
3783 heap_free(thisop);
3784 return rc;
3788 /****************************************************************************
3789 * WCMD_handleExpression
3790 * Handles an expression provided to set /a - If it finds brackets, it uses
3791 * recursion to process the parts in brackets.
3793 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3795 static const WCHAR mathDelims[] = {' ','\t','(',')','!','~','-','*','/','%',
3796 '+','<','>','&','^','|','=',',','\0' };
3797 int rc = 0;
3798 WCHAR *pos;
3799 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3800 OPSTACK *opstackhead = NULL;
3801 VARSTACK *varstackhead = NULL;
3802 WCHAR foundhalf = 0;
3804 /* Initialize */
3805 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3806 pos = *expr;
3808 /* Iterate through until whole expression is processed */
3809 while (pos && *pos) {
3810 BOOL treatasnumber;
3812 /* Skip whitespace to get to the next character to process*/
3813 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3814 if (!*pos) goto exprreturn;
3816 /* If we have found anything other than an operator then it's a number/variable */
3817 if (strchrW(mathDelims, *pos) == NULL) {
3818 WCHAR *parmstart, *parm, *dupparm;
3819 WCHAR *nextpos;
3821 /* Cannot have an expression with var/number twice, without an operator
3822 in-between, nor or number following a half constructed << or >> operator */
3823 if (lastwasnumber || foundhalf) {
3824 rc = WCMD_NOOPERATOR;
3825 goto exprerrorreturn;
3827 lastwasnumber = TRUE;
3829 if (isdigitW(*pos)) {
3830 /* For a number - just push it onto the stack */
3831 int num = strtoulW(pos, &nextpos, 0);
3832 WCMD_pushnumber(NULL, num, &varstackhead);
3833 pos = nextpos;
3835 /* Verify the number was validly formed */
3836 if (*nextpos && (strchrW(mathDelims, *nextpos) == NULL)) {
3837 rc = WCMD_BADHEXOCT;
3838 goto exprerrorreturn;
3840 } else {
3842 /* For a variable - just push it onto the stack */
3843 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3844 dupparm = heap_strdupW(parm);
3845 WCMD_pushnumber(dupparm, 0, &varstackhead);
3846 pos = parmstart + strlenW(dupparm);
3848 continue;
3851 /* We have found an operator. Some operators are one character, some two, and the minus
3852 and plus signs need special processing as they can be either operators or just influence
3853 the parameter which follows them */
3854 if (foundhalf && (*pos != foundhalf)) {
3855 /* Badly constructed operator pair */
3856 rc = WCMD_NOOPERATOR;
3857 goto exprerrorreturn;
3860 treatasnumber = FALSE; /* We are processing an operand */
3861 switch (*pos) {
3863 /* > and < are special as they are double character operators (and spaces can be between them!)
3864 If we see these for the first time, set a flag, and second time around we continue.
3865 Note these double character operators are stored as just one of the characters on the stack */
3866 case '>':
3867 case '<': if (!foundhalf) {
3868 foundhalf = *pos;
3869 pos++;
3870 break;
3872 /* We have found the rest, so clear up the knowledge of the half completed part and
3873 drop through to normal operator processing */
3874 foundhalf = 0;
3875 /* drop through */
3877 case '=': if (*pos=='=') {
3878 /* = is special cased as if the last was an operator then we may have e.g. += or
3879 *= etc which we need to handle by replacing the operator that is on the stack
3880 with a calculated assignment equivalent */
3881 if (!lastwasnumber && opstackhead) {
3882 int i = 0;
3883 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3884 i++;
3886 if (calcassignments[i].op == ' ') {
3887 rc = WCMD_NOOPERAND;
3888 goto exprerrorreturn;
3889 } else {
3890 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3891 when the general operator handling happens further down. */
3892 *pos = calcassignments[i].calculatedop;
3893 WCMD_popoperator(&opstackhead);
3897 /* Drop though */
3899 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3900 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3901 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
3902 /* drop through */
3903 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
3904 /* drop through */
3906 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3907 case '!': /* drop through */
3908 case '~': /* drop through */
3909 case '/': /* drop through */
3910 case '%': /* drop through */
3911 case '&': /* drop through */
3912 case '^': /* drop through */
3913 case '*': /* drop through */
3914 case '|':
3915 /* General code for handling most of the operators - look at the
3916 precedence of the top item on the stack, and see if we need to
3917 action the stack before we push something else onto it. */
3919 int precedence = WCMD_getprecedence(*pos);
3920 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
3921 precedence, !opstackhead?-1:opstackhead->precedence);
3923 /* In general, for things with the same precedence, reduce immediately
3924 except for assignments and unary operators which do not */
3925 while (!rc && opstackhead &&
3926 ((opstackhead->precedence > precedence) ||
3927 ((opstackhead->precedence == precedence) &&
3928 (precedence != 1) && (precedence != 8)))) {
3929 rc = WCMD_reduce(&opstackhead, &varstackhead);
3931 if (rc) goto exprerrorreturn;
3932 WCMD_pushoperator(*pos, precedence, &opstackhead);
3933 pos++;
3934 break;
3937 /* comma means start a new expression, ie calculate what we have */
3938 case ',':
3940 int prevresult = -1;
3941 WINE_TRACE("Found expression delimiter - reducing exising stacks\n");
3942 while (!rc && opstackhead) {
3943 rc = WCMD_reduce(&opstackhead, &varstackhead);
3945 if (rc) goto exprerrorreturn;
3946 /* If we have anything other than one number left, error
3947 otherwise throw the number away */
3948 if (!varstackhead || varstackhead->next) {
3949 rc = WCMD_NOOPERATOR;
3950 goto exprerrorreturn;
3952 prevresult = WCMD_popnumber(&varstackhead);
3953 WINE_TRACE("Expression resolved to %d\n", prevresult);
3954 heap_free(varstackhead);
3955 varstackhead = NULL;
3956 pos++;
3957 break;
3960 /* Open bracket - use iteration to parse the inner expression, then continue */
3961 case '(' : {
3962 int exprresult = 0;
3963 pos++;
3964 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
3965 if (rc) goto exprerrorreturn;
3966 WCMD_pushnumber(NULL, exprresult, &varstackhead);
3967 break;
3970 /* Close bracket - we have finished this depth, calculate and return */
3971 case ')' : {
3972 pos++;
3973 treatasnumber = TRUE; /* Things in brackets result in a number */
3974 if (depth == 0) {
3975 rc = WCMD_BADPAREN;
3976 goto exprerrorreturn;
3978 goto exprreturn;
3981 default:
3982 WINE_ERR("Unrecognized operator %c\n", *pos);
3983 pos++;
3985 lastwasnumber = treatasnumber;
3988 exprreturn:
3989 *expr = pos;
3991 /* We need to reduce until we have a single number (or variable) on the
3992 stack and set the return value to that */
3993 while (!rc && opstackhead) {
3994 rc = WCMD_reduce(&opstackhead, &varstackhead);
3996 if (rc) goto exprerrorreturn;
3998 /* If we have anything other than one number left, error
3999 otherwise throw the number away */
4000 if (!varstackhead || varstackhead->next) {
4001 rc = WCMD_NOOPERATOR;
4002 goto exprerrorreturn;
4005 /* Now get the number (and convert if it's just a variable name) */
4006 *ret = WCMD_popnumber(&varstackhead);
4008 exprerrorreturn:
4009 /* Free all remaining memory */
4010 while (opstackhead) WCMD_popoperator(&opstackhead);
4011 while (varstackhead) WCMD_popnumber(&varstackhead);
4013 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
4014 return rc;
4017 /****************************************************************************
4018 * WCMD_setshow_env
4020 * Set/Show the environment variables
4023 void WCMD_setshow_env (WCHAR *s) {
4025 LPVOID env;
4026 WCHAR *p;
4027 BOOL status;
4028 static const WCHAR parmP[] = {'/','P','\0'};
4029 static const WCHAR parmA[] = {'/','A','\0'};
4030 WCHAR string[MAXSTRING];
4032 if (param1[0] == 0x00 && quals[0] == 0x00) {
4033 env = GetEnvironmentStringsW();
4034 WCMD_setshow_sortenv( env, NULL );
4035 return;
4038 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4039 if (CompareStringW(LOCALE_USER_DEFAULT,
4040 NORM_IGNORECASE | SORT_STRINGSORT,
4041 s, 2, parmP, -1) == CSTR_EQUAL) {
4042 DWORD count;
4044 s += 2;
4045 while (*s && (*s==' ' || *s=='\t')) s++;
4046 /* set /P "var=value"jim ignores anything after the last quote */
4047 if (*s=='\"') {
4048 WCHAR *lastquote;
4049 lastquote = WCMD_strip_quotes(s);
4050 if (lastquote) *lastquote = 0x00;
4051 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4054 /* If no parameter, or no '=' sign, return an error */
4055 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
4056 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4057 return;
4060 /* Output the prompt */
4061 *p++ = '\0';
4062 if (strlenW(p) != 0) WCMD_output_asis(p);
4064 /* Read the reply */
4065 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4066 if (count > 1) {
4067 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4068 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4069 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4070 wine_dbgstr_w(string));
4071 status = SetEnvironmentVariableW(s, string);
4074 /* See if /A supplied, and if so calculate the results of all the expressions */
4075 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4076 NORM_IGNORECASE | SORT_STRINGSORT,
4077 s, 2, parmA, -1) == CSTR_EQUAL) {
4078 /* /A supplied, so evaluate expressions and set variables appropriately */
4079 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4080 /* of the final computation */
4081 int result = 0;
4082 int rc = 0;
4083 WCHAR *thisexpr;
4084 WCHAR *src,*dst;
4086 /* Remove all quotes before doing any calculations */
4087 thisexpr = heap_alloc((strlenW(s+2)+1) * sizeof(WCHAR));
4088 src = s+2;
4089 dst = thisexpr;
4090 while (*src) {
4091 if (*src != '"') *dst++ = *src;
4092 src++;
4094 *dst = 0;
4096 /* Now calculate the results of the expression */
4097 src = thisexpr;
4098 rc = WCMD_handleExpression(&src, &result, 0);
4099 heap_free(thisexpr);
4101 /* If parsing failed, issue the error message */
4102 if (rc > 0) {
4103 WCMD_output_stderr(WCMD_LoadMessage(rc));
4104 return;
4107 /* If we have no context (interactive or cmd.exe /c) print the final result */
4108 if (!context) {
4109 static const WCHAR fmt[] = {'%','d','\0'};
4110 sprintfW(string, fmt, result);
4111 WCMD_output_asis(string);
4114 } else {
4115 DWORD gle;
4117 /* set "var=value"jim ignores anything after the last quote */
4118 if (*s=='\"') {
4119 WCHAR *lastquote;
4120 lastquote = WCMD_strip_quotes(s);
4121 if (lastquote) *lastquote = 0x00;
4122 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4125 p = strchrW (s, '=');
4126 if (p == NULL) {
4127 env = GetEnvironmentStringsW();
4128 if (WCMD_setshow_sortenv( env, s ) == 0) {
4129 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4130 errorlevel = 1;
4132 return;
4134 *p++ = '\0';
4136 if (strlenW(p) == 0) p = NULL;
4137 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4138 wine_dbgstr_w(p));
4139 status = SetEnvironmentVariableW(s, p);
4140 gle = GetLastError();
4141 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4142 errorlevel = 1;
4143 } else if (!status) WCMD_print_error();
4144 else errorlevel = 0;
4148 /****************************************************************************
4149 * WCMD_setshow_path
4151 * Set/Show the path environment variable
4154 void WCMD_setshow_path (const WCHAR *args) {
4156 WCHAR string[1024];
4157 DWORD status;
4158 static const WCHAR pathW[] = {'P','A','T','H','\0'};
4159 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
4161 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
4162 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
4163 if (status != 0) {
4164 WCMD_output_asis ( pathEqW);
4165 WCMD_output_asis ( string);
4166 WCMD_output_asis ( newlineW);
4168 else {
4169 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4172 else {
4173 if (*args == '=') args++; /* Skip leading '=' */
4174 status = SetEnvironmentVariableW(pathW, args);
4175 if (!status) WCMD_print_error();
4179 /****************************************************************************
4180 * WCMD_setshow_prompt
4182 * Set or show the command prompt.
4185 void WCMD_setshow_prompt (void) {
4187 WCHAR *s;
4188 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
4190 if (strlenW(param1) == 0) {
4191 SetEnvironmentVariableW(promptW, NULL);
4193 else {
4194 s = param1;
4195 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4196 if (strlenW(s) == 0) {
4197 SetEnvironmentVariableW(promptW, NULL);
4199 else SetEnvironmentVariableW(promptW, s);
4203 /****************************************************************************
4204 * WCMD_setshow_time
4206 * Set/Show the system time
4207 * FIXME: Can't change time yet
4210 void WCMD_setshow_time (void) {
4212 WCHAR curtime[64], buffer[64];
4213 DWORD count;
4214 SYSTEMTIME st;
4215 static const WCHAR parmT[] = {'/','T','\0'};
4217 if (strlenW(param1) == 0) {
4218 GetLocalTime(&st);
4219 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
4220 curtime, sizeof(curtime)/sizeof(WCHAR))) {
4221 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4222 if (strstrW (quals, parmT) == NULL) {
4223 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4224 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4225 if (count > 2) {
4226 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4230 else WCMD_print_error ();
4232 else {
4233 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4237 /****************************************************************************
4238 * WCMD_shift
4240 * Shift batch parameters.
4241 * Optional /n says where to start shifting (n=0-8)
4244 void WCMD_shift (const WCHAR *args) {
4245 int start;
4247 if (context != NULL) {
4248 WCHAR *pos = strchrW(args, '/');
4249 int i;
4251 if (pos == NULL) {
4252 start = 0;
4253 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4254 start = (*(pos+1) - '0');
4255 } else {
4256 SetLastError(ERROR_INVALID_PARAMETER);
4257 WCMD_print_error();
4258 return;
4261 WINE_TRACE("Shifting variables, starting at %d\n", start);
4262 for (i=start;i<=8;i++) {
4263 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4265 context -> shift_count[9] = context -> shift_count[9] + 1;
4270 /****************************************************************************
4271 * WCMD_start
4273 void WCMD_start(const WCHAR *args)
4275 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
4276 '\\','s','t','a','r','t','.','e','x','e',0};
4277 WCHAR file[MAX_PATH];
4278 WCHAR *cmdline;
4279 STARTUPINFOW st;
4280 PROCESS_INFORMATION pi;
4282 GetWindowsDirectoryW( file, MAX_PATH );
4283 strcatW( file, exeW );
4284 cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
4285 strcpyW( cmdline, file );
4286 strcatW( cmdline, spaceW );
4287 strcatW( cmdline, args );
4289 memset( &st, 0, sizeof(STARTUPINFOW) );
4290 st.cb = sizeof(STARTUPINFOW);
4292 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4294 WaitForSingleObject( pi.hProcess, INFINITE );
4295 GetExitCodeProcess( pi.hProcess, &errorlevel );
4296 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4297 CloseHandle(pi.hProcess);
4298 CloseHandle(pi.hThread);
4300 else
4302 SetLastError(ERROR_FILE_NOT_FOUND);
4303 WCMD_print_error ();
4304 errorlevel = 9009;
4306 heap_free(cmdline);
4309 /****************************************************************************
4310 * WCMD_title
4312 * Set the console title
4314 void WCMD_title (const WCHAR *args) {
4315 SetConsoleTitleW(args);
4318 /****************************************************************************
4319 * WCMD_type
4321 * Copy a file to standard output.
4324 void WCMD_type (WCHAR *args) {
4326 int argno = 0;
4327 WCHAR *argN = args;
4328 BOOL writeHeaders = FALSE;
4330 if (param1[0] == 0x00) {
4331 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4332 return;
4335 if (param2[0] != 0x00) writeHeaders = TRUE;
4337 /* Loop through all args */
4338 errorlevel = 0;
4339 while (argN) {
4340 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4342 HANDLE h;
4343 WCHAR buffer[512];
4344 DWORD count;
4346 if (!argN) break;
4348 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4349 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4350 FILE_ATTRIBUTE_NORMAL, NULL);
4351 if (h == INVALID_HANDLE_VALUE) {
4352 WCMD_print_error ();
4353 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4354 errorlevel = 1;
4355 } else {
4356 if (writeHeaders) {
4357 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
4358 WCMD_output(fmt, thisArg);
4360 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
4361 if (count == 0) break; /* ReadFile reports success on EOF! */
4362 buffer[count] = 0;
4363 WCMD_output_asis (buffer);
4365 CloseHandle (h);
4370 /****************************************************************************
4371 * WCMD_more
4373 * Output either a file or stdin to screen in pages
4376 void WCMD_more (WCHAR *args) {
4378 int argno = 0;
4379 WCHAR *argN = args;
4380 WCHAR moreStr[100];
4381 WCHAR moreStrPage[100];
4382 WCHAR buffer[512];
4383 DWORD count;
4384 static const WCHAR moreStart[] = {'-','-',' ','\0'};
4385 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
4386 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
4387 ')',' ','-','-','\n','\0'};
4388 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
4390 /* Prefix the NLS more with '-- ', then load the text */
4391 errorlevel = 0;
4392 strcpyW(moreStr, moreStart);
4393 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
4394 (sizeof(moreStr)/sizeof(WCHAR))-3);
4396 if (param1[0] == 0x00) {
4398 /* Wine implements pipes via temporary files, and hence stdin is
4399 effectively reading from the file. This means the prompts for
4400 more are satisfied by the next line from the input (file). To
4401 avoid this, ensure stdin is to the console */
4402 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4403 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
4404 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4405 FILE_ATTRIBUTE_NORMAL, 0);
4406 WINE_TRACE("No parms - working probably in pipe mode\n");
4407 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4409 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4410 once you get in this bit unless due to a pipe, it's going to end badly... */
4411 wsprintfW(moreStrPage, moreFmt, moreStr);
4413 WCMD_enter_paged_mode(moreStrPage);
4414 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4415 if (count == 0) break; /* ReadFile reports success on EOF! */
4416 buffer[count] = 0;
4417 WCMD_output_asis (buffer);
4419 WCMD_leave_paged_mode();
4421 /* Restore stdin to what it was */
4422 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4423 CloseHandle(hConIn);
4425 return;
4426 } else {
4427 BOOL needsPause = FALSE;
4429 /* Loop through all args */
4430 WINE_TRACE("Parms supplied - working through each file\n");
4431 WCMD_enter_paged_mode(moreStrPage);
4433 while (argN) {
4434 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4435 HANDLE h;
4437 if (!argN) break;
4439 if (needsPause) {
4441 /* Wait */
4442 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
4443 WCMD_leave_paged_mode();
4444 WCMD_output_asis(moreStrPage);
4445 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4446 WCMD_enter_paged_mode(moreStrPage);
4450 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4451 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4452 FILE_ATTRIBUTE_NORMAL, NULL);
4453 if (h == INVALID_HANDLE_VALUE) {
4454 WCMD_print_error ();
4455 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4456 errorlevel = 1;
4457 } else {
4458 ULONG64 curPos = 0;
4459 ULONG64 fileLen = 0;
4460 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4462 /* Get the file size */
4463 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4464 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4466 needsPause = TRUE;
4467 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4468 if (count == 0) break; /* ReadFile reports success on EOF! */
4469 buffer[count] = 0;
4470 curPos += count;
4472 /* Update % count (would be used in WCMD_output_asis as prompt) */
4473 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
4475 WCMD_output_asis (buffer);
4477 CloseHandle (h);
4481 WCMD_leave_paged_mode();
4485 /****************************************************************************
4486 * WCMD_verify
4488 * Display verify flag.
4489 * FIXME: We don't actually do anything with the verify flag other than toggle
4490 * it...
4493 void WCMD_verify (const WCHAR *args) {
4495 int count;
4497 count = strlenW(args);
4498 if (count == 0) {
4499 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
4500 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
4501 return;
4503 if (lstrcmpiW(args, onW) == 0) {
4504 verify_mode = TRUE;
4505 return;
4507 else if (lstrcmpiW(args, offW) == 0) {
4508 verify_mode = FALSE;
4509 return;
4511 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4514 /****************************************************************************
4515 * WCMD_version
4517 * Display version info.
4520 void WCMD_version (void) {
4522 WCMD_output_asis (version_string);
4526 /****************************************************************************
4527 * WCMD_volume
4529 * Display volume information (set_label = FALSE)
4530 * Additionally set volume label (set_label = TRUE)
4531 * Returns 1 on success, 0 otherwise
4534 int WCMD_volume(BOOL set_label, const WCHAR *path)
4536 DWORD count, serial;
4537 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4538 BOOL status;
4540 if (strlenW(path) == 0) {
4541 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
4542 if (!status) {
4543 WCMD_print_error ();
4544 return 0;
4546 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
4547 &serial, NULL, NULL, NULL, 0);
4549 else {
4550 static const WCHAR fmt[] = {'%','s','\\','\0'};
4551 if ((path[1] != ':') || (strlenW(path) != 2)) {
4552 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4553 return 0;
4555 wsprintfW (curdir, fmt, path);
4556 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
4557 &serial, NULL,
4558 NULL, NULL, 0);
4560 if (!status) {
4561 WCMD_print_error ();
4562 return 0;
4564 if (label[0] != '\0') {
4565 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4566 curdir[0], label);
4568 else {
4569 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4570 curdir[0]);
4572 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4573 HIWORD(serial), LOWORD(serial));
4574 if (set_label) {
4575 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4576 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4577 if (count > 1) {
4578 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4579 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4581 if (strlenW(path) != 0) {
4582 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4584 else {
4585 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4588 return 1;
4591 /**************************************************************************
4592 * WCMD_exit
4594 * Exit either the process, or just this batch program
4598 void WCMD_exit (CMD_LIST **cmdList) {
4600 static const WCHAR parmB[] = {'/','B','\0'};
4601 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
4603 if (context && lstrcmpiW(quals, parmB) == 0) {
4604 errorlevel = rc;
4605 context -> skip_rest = TRUE;
4606 *cmdList = NULL;
4607 } else {
4608 ExitProcess(rc);
4613 /*****************************************************************************
4614 * WCMD_assoc
4616 * Lists or sets file associations (assoc = TRUE)
4617 * Lists or sets file types (assoc = FALSE)
4619 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4621 HKEY key;
4622 DWORD accessOptions = KEY_READ;
4623 WCHAR *newValue;
4624 LONG rc = ERROR_SUCCESS;
4625 WCHAR keyValue[MAXSTRING];
4626 DWORD valueLen = MAXSTRING;
4627 HKEY readKey;
4628 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
4629 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4631 /* See if parameter includes '=' */
4632 errorlevel = 0;
4633 newValue = strchrW(args, '=');
4634 if (newValue) accessOptions |= KEY_WRITE;
4636 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4637 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
4638 accessOptions, &key) != ERROR_SUCCESS) {
4639 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4640 return;
4643 /* If no parameters then list all associations */
4644 if (*args == 0x00) {
4645 int index = 0;
4647 /* Enumerate all the keys */
4648 while (rc != ERROR_NO_MORE_ITEMS) {
4649 WCHAR keyName[MAXSTRING];
4650 DWORD nameLen;
4652 /* Find the next value */
4653 nameLen = MAXSTRING;
4654 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4656 if (rc == ERROR_SUCCESS) {
4658 /* Only interested in extension ones if assoc, or others
4659 if not assoc */
4660 if ((keyName[0] == '.' && assoc) ||
4661 (!(keyName[0] == '.') && (!assoc)))
4663 WCHAR subkey[MAXSTRING];
4664 strcpyW(subkey, keyName);
4665 if (!assoc) strcatW(subkey, shOpCmdW);
4667 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4669 valueLen = sizeof(keyValue)/sizeof(WCHAR);
4670 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4671 WCMD_output_asis(keyName);
4672 WCMD_output_asis(equalW);
4673 /* If no default value found, leave line empty after '=' */
4674 if (rc == ERROR_SUCCESS) {
4675 WCMD_output_asis(keyValue);
4677 WCMD_output_asis(newlineW);
4678 RegCloseKey(readKey);
4684 } else {
4686 /* Parameter supplied - if no '=' on command line, it's a query */
4687 if (newValue == NULL) {
4688 WCHAR *space;
4689 WCHAR subkey[MAXSTRING];
4691 /* Query terminates the parameter at the first space */
4692 strcpyW(keyValue, args);
4693 space = strchrW(keyValue, ' ');
4694 if (space) *space=0x00;
4696 /* Set up key name */
4697 strcpyW(subkey, keyValue);
4698 if (!assoc) strcatW(subkey, shOpCmdW);
4700 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4702 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4703 WCMD_output_asis(args);
4704 WCMD_output_asis(equalW);
4705 /* If no default value found, leave line empty after '=' */
4706 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4707 WCMD_output_asis(newlineW);
4708 RegCloseKey(readKey);
4710 } else {
4711 WCHAR msgbuffer[MAXSTRING];
4713 /* Load the translated 'File association not found' */
4714 if (assoc) {
4715 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4716 } else {
4717 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4719 WCMD_output_stderr(msgbuffer, keyValue);
4720 errorlevel = 2;
4723 /* Not a query - it's a set or clear of a value */
4724 } else {
4726 WCHAR subkey[MAXSTRING];
4728 /* Get pointer to new value */
4729 *newValue = 0x00;
4730 newValue++;
4732 /* Set up key name */
4733 strcpyW(subkey, args);
4734 if (!assoc) strcatW(subkey, shOpCmdW);
4736 /* If nothing after '=' then clear value - only valid for ASSOC */
4737 if (*newValue == 0x00) {
4739 if (assoc) rc = RegDeleteKeyW(key, args);
4740 if (assoc && rc == ERROR_SUCCESS) {
4741 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4743 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4744 WCMD_print_error();
4745 errorlevel = 2;
4747 } else {
4748 WCHAR msgbuffer[MAXSTRING];
4750 /* Load the translated 'File association not found' */
4751 if (assoc) {
4752 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
4753 sizeof(msgbuffer)/sizeof(WCHAR));
4754 } else {
4755 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
4756 sizeof(msgbuffer)/sizeof(WCHAR));
4758 WCMD_output_stderr(msgbuffer, keyValue);
4759 errorlevel = 2;
4762 /* It really is a set value = contents */
4763 } else {
4764 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4765 accessOptions, NULL, &readKey, NULL);
4766 if (rc == ERROR_SUCCESS) {
4767 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4768 (LPBYTE)newValue,
4769 sizeof(WCHAR) * (strlenW(newValue) + 1));
4770 RegCloseKey(readKey);
4773 if (rc != ERROR_SUCCESS) {
4774 WCMD_print_error();
4775 errorlevel = 2;
4776 } else {
4777 WCMD_output_asis(args);
4778 WCMD_output_asis(equalW);
4779 WCMD_output_asis(newValue);
4780 WCMD_output_asis(newlineW);
4786 /* Clean up */
4787 RegCloseKey(key);
4790 /****************************************************************************
4791 * WCMD_color
4793 * Colors the terminal screen.
4796 void WCMD_color (void) {
4798 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4799 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4801 if (param1[0] != 0x00 && strlenW(param1) > 2) {
4802 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4803 return;
4806 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4808 COORD topLeft;
4809 DWORD screenSize;
4810 DWORD color = 0;
4812 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4814 topLeft.X = 0;
4815 topLeft.Y = 0;
4817 /* Convert the color hex digits */
4818 if (param1[0] == 0x00) {
4819 color = defaultColor;
4820 } else {
4821 color = strtoulW(param1, NULL, 16);
4824 /* Fail if fg == bg color */
4825 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4826 errorlevel = 1;
4827 return;
4830 /* Set the current screen contents and ensure all future writes
4831 remain this color */
4832 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4833 SetConsoleTextAttribute(hStdOut, color);