msi: Don't defer custom actions in the UI sequence if they match the currently runnin...
[wine/multimedia.git] / programs / cmd / builtins.c
blob1d9fc15d7754134ec71ef764c6688a27d54f83a7
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 fslashW[] = {'/','\0'};
104 static const WCHAR onW[] = {'O','N','\0'};
105 static const WCHAR offW[] = {'O','F','F','\0'};
106 static const WCHAR parmY[] = {'/','Y','\0'};
107 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
108 static const WCHAR eqeqW[] = {'=','=','\0'};
110 static HINSTANCE hinst;
111 struct env_stack *saved_environment;
112 static BOOL verify_mode = FALSE;
114 /* set /a routines work from single character operators, but some of the
115 operators are multiple character ones, especially the assignment ones.
116 Temporarily represent these using the values below on the operator stack */
117 #define OP_POSITIVE 'P'
118 #define OP_NEGATIVE 'N'
119 #define OP_ASSSIGNMUL 'a'
120 #define OP_ASSSIGNDIV 'b'
121 #define OP_ASSSIGNMOD 'c'
122 #define OP_ASSSIGNADD 'd'
123 #define OP_ASSSIGNSUB 'e'
124 #define OP_ASSSIGNAND 'f'
125 #define OP_ASSSIGNNOT 'g'
126 #define OP_ASSSIGNOR 'h'
127 #define OP_ASSSIGNSHL 'i'
128 #define OP_ASSSIGNSHR 'j'
130 /* This maintains a stack of operators, holding both the operator precedence
131 and the single character representation of the operator in question */
132 typedef struct _OPSTACK
134 int precedence;
135 WCHAR op;
136 struct _OPSTACK *next;
137 } OPSTACK;
139 /* This maintains a stack of values, where each value can either be a
140 numeric value, or a string represeting an environment variable */
141 typedef struct _VARSTACK
143 BOOL isnum;
144 WCHAR *variable;
145 int value;
146 struct _VARSTACK *next;
147 } VARSTACK;
149 /* This maintains a mapping between the calculated operator and the
150 single character representation for the assignment operators. */
151 static struct
153 WCHAR op;
154 WCHAR calculatedop;
155 } calcassignments[] =
157 {'*', OP_ASSSIGNMUL},
158 {'/', OP_ASSSIGNDIV},
159 {'%', OP_ASSSIGNMOD},
160 {'+', OP_ASSSIGNADD},
161 {'-', OP_ASSSIGNSUB},
162 {'&', OP_ASSSIGNAND},
163 {'^', OP_ASSSIGNNOT},
164 {'|', OP_ASSSIGNOR},
165 {'<', OP_ASSSIGNSHL},
166 {'>', OP_ASSSIGNSHR},
167 {' ',' '}
170 /**************************************************************************
171 * WCMD_ask_confirm
173 * Issue a message and ask for confirmation, waiting on a valid answer.
175 * Returns True if Y (or A) answer is selected
176 * If optionAll contains a pointer, ALL is allowed, and if answered
177 * set to TRUE
180 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
181 BOOL *optionAll) {
183 UINT msgid;
184 WCHAR confirm[MAXSTRING];
185 WCHAR options[MAXSTRING];
186 WCHAR Ybuffer[MAXSTRING];
187 WCHAR Nbuffer[MAXSTRING];
188 WCHAR Abuffer[MAXSTRING];
189 WCHAR answer[MAX_PATH] = {'\0'};
190 DWORD count = 0;
192 /* Load the translated valid answers */
193 if (showSureText)
194 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
195 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
196 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
197 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
198 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
199 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
201 /* Loop waiting on a valid answer */
202 if (optionAll)
203 *optionAll = FALSE;
204 while (1)
206 WCMD_output_asis (message);
207 if (showSureText)
208 WCMD_output_asis (confirm);
209 WCMD_output_asis (options);
210 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
211 answer[0] = toupperW(answer[0]);
212 if (answer[0] == Ybuffer[0])
213 return TRUE;
214 if (answer[0] == Nbuffer[0])
215 return FALSE;
216 if (optionAll && answer[0] == Abuffer[0])
218 *optionAll = TRUE;
219 return TRUE;
224 /****************************************************************************
225 * WCMD_clear_screen
227 * Clear the terminal screen.
230 void WCMD_clear_screen (void) {
232 /* Emulate by filling the screen from the top left to bottom right with
233 spaces, then moving the cursor to the top left afterwards */
234 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
235 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
237 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
239 COORD topLeft;
240 DWORD screenSize;
242 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
244 topLeft.X = 0;
245 topLeft.Y = 0;
246 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
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 DWORD 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 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
405 if (!opt_s)
406 answer[0] = toupperW(answer[0]);
408 ptr = strchrW(opt_c, answer[0]);
409 if (ptr) {
410 WCMD_output_asis(answer);
411 WCMD_output_asis(newlineW);
412 if (have_console)
413 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
415 errorlevel = (ptr - opt_c) + 1;
416 WINE_TRACE("answer: %d\n", errorlevel);
417 heap_free(my_command);
418 return;
420 else
422 /* key not allowed: play the bell */
423 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
424 WCMD_output_asis(bellW);
429 /****************************************************************************
430 * WCMD_AppendEOF
432 * Adds an EOF onto the end of a file
433 * Returns TRUE on success
435 static BOOL WCMD_AppendEOF(WCHAR *filename)
437 HANDLE h;
439 char eof = '\x1a';
441 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
442 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
443 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
445 if (h == NULL) {
446 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
447 return FALSE;
448 } else {
449 SetFilePointer (h, 0, NULL, FILE_END);
450 if (!WriteFile(h, &eof, 1, NULL, NULL)) {
451 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
452 CloseHandle(h);
453 return FALSE;
455 CloseHandle(h);
457 return TRUE;
460 /****************************************************************************
461 * WCMD_ManualCopy
463 * Copies from a file
464 * optionally reading only until EOF (ascii copy)
465 * optionally appending onto an existing file (append)
466 * Returns TRUE on success
468 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
470 HANDLE in,out;
471 BOOL ok;
472 DWORD bytesread, byteswritten;
474 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
475 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
477 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
478 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
479 if (in == NULL) {
480 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
481 return FALSE;
484 /* Open the output file, overwriting if not appending */
485 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
486 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
487 if (out == NULL) {
488 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
489 CloseHandle(in);
490 return FALSE;
493 /* Move to end of destination if we are going to append to it */
494 if (append) {
495 SetFilePointer(out, 0, NULL, FILE_END);
498 /* Loop copying data from source to destination until EOF read */
501 char buffer[MAXSTRING];
503 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
504 if (ok) {
506 /* Stop at first EOF */
507 if (ascii) {
508 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
509 if (ptr) bytesread = (ptr - buffer);
512 if (bytesread) {
513 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
514 if (!ok || byteswritten != bytesread) {
515 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
516 wine_dbgstr_w(dstname), GetLastError());
519 } else {
520 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
521 wine_dbgstr_w(srcname), GetLastError());
523 } while (ok && bytesread > 0);
525 CloseHandle(out);
526 CloseHandle(in);
527 return ok;
530 /****************************************************************************
531 * WCMD_copy
533 * Copy a file or wildcarded set.
534 * For ascii/binary type copies, it gets complex:
535 * Syntax on command line is
536 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
537 * Where first /a or /b sets 'mode in operation' until another is found
538 * once another is found, it applies to the file preceding the /a or /b
539 * In addition each filename can contain wildcards
540 * To make matters worse, the + may be in the same parameter (i.e. no
541 * whitespace) or with whitespace separating it
543 * ASCII mode on read == read and stop at first EOF
544 * ASCII mode on write == append EOF to destination
545 * Binary == copy as-is
547 * Design of this is to build up a list of files which will be copied into a
548 * list, then work through the list file by file.
549 * If no destination is specified, it defaults to the name of the first file in
550 * the list, but the current directory.
554 void WCMD_copy(WCHAR * args) {
556 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
557 WCHAR *thisparam;
558 int argno = 0;
559 WCHAR *rawarg;
560 WIN32_FIND_DATAW fd;
561 HANDLE hff = INVALID_HANDLE_VALUE;
562 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
563 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
564 BOOL anyconcats = FALSE; /* Have we found any + options */
565 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
566 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
567 BOOL prompt; /* Prompt before overwriting */
568 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
569 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
570 BOOL status;
571 WCHAR copycmd[4];
572 DWORD len;
573 BOOL dstisdevice = FALSE;
574 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
576 typedef struct _COPY_FILES
578 struct _COPY_FILES *next;
579 BOOL concatenate;
580 WCHAR *name;
581 int binarycopy;
582 } COPY_FILES;
583 COPY_FILES *sourcelist = NULL;
584 COPY_FILES *lastcopyentry = NULL;
585 COPY_FILES *destination = NULL;
586 COPY_FILES *thiscopy = NULL;
587 COPY_FILES *prevcopy = NULL;
589 /* Assume we were successful! */
590 errorlevel = 0;
592 /* If no args supplied at all, report an error */
593 if (param1[0] == 0x00) {
594 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
595 errorlevel = 1;
596 return;
599 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
601 /* Walk through all args, building up a list of files to process */
602 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
603 while (*(thisparam)) {
604 WCHAR *pos1, *pos2;
605 BOOL inquotes;
607 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
609 /* Handle switches */
610 if (*thisparam == '/') {
611 while (*thisparam == '/') {
612 thisparam++;
613 if (toupperW(*thisparam) == 'D') {
614 opt_d = TRUE;
615 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
616 } else if (toupperW(*thisparam) == 'Y') {
617 opt_y = TRUE;
618 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
619 opt_noty = TRUE;
620 } else if (toupperW(*thisparam) == 'V') {
621 opt_v = TRUE;
622 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
623 } else if (toupperW(*thisparam) == 'N') {
624 opt_n = TRUE;
625 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
626 } else if (toupperW(*thisparam) == 'Z') {
627 opt_z = TRUE;
628 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
629 } else if (toupperW(*thisparam) == 'A') {
630 if (binarymode != 0) {
631 binarymode = 0;
632 WINE_TRACE("Subsequent files will be handled as ASCII\n");
633 if (destination != NULL) {
634 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
635 destination->binarycopy = binarymode;
636 } else if (lastcopyentry != NULL) {
637 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
638 lastcopyentry->binarycopy = binarymode;
641 } else if (toupperW(*thisparam) == 'B') {
642 if (binarymode != 1) {
643 binarymode = 1;
644 WINE_TRACE("Subsequent files will be handled as binary\n");
645 if (destination != NULL) {
646 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
647 destination->binarycopy = binarymode;
648 } else if (lastcopyentry != NULL) {
649 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
650 lastcopyentry->binarycopy = binarymode;
653 } else {
654 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
656 thisparam++;
659 /* This parameter was purely switches, get the next one */
660 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
661 continue;
664 /* We have found something which is not a switch. If could be anything of the form
665 sourcefilename (which could be destination too)
666 + (when filename + filename syntex used)
667 sourcefilename+sourcefilename
668 +sourcefilename
669 +/b[tests show windows then ignores to end of parameter]
672 if (*thisparam=='+') {
673 if (lastcopyentry == NULL) {
674 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
675 errorlevel = 1;
676 goto exitreturn;
677 } else {
678 concatnextfilename = TRUE;
679 anyconcats = TRUE;
682 /* Move to next thing to process */
683 thisparam++;
684 if (*thisparam == 0x00)
685 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
686 continue;
689 /* We have found something to process - build a COPY_FILE block to store it */
690 thiscopy = heap_alloc(sizeof(COPY_FILES));
692 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
693 thiscopy->concatenate = concatnextfilename;
694 thiscopy->binarycopy = binarymode;
695 thiscopy->next = NULL;
697 /* Time to work out the name. Allocate at least enough space (deliberately too much to
698 leave space to append \* to the end) , then copy in character by character. Strip off
699 quotes if we find them. */
700 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
701 thiscopy->name = heap_alloc(len*sizeof(WCHAR));
702 memset(thiscopy->name, 0x00, len);
704 pos1 = thisparam;
705 pos2 = thiscopy->name;
706 inquotes = FALSE;
707 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
708 if (*pos1 == '"') {
709 inquotes = !inquotes;
710 pos1++;
711 } else *pos2++ = *pos1++;
713 *pos2 = 0;
714 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
716 /* This is either the first source, concatenated subsequent source or destination */
717 if (sourcelist == NULL) {
718 WINE_TRACE("Adding as first source part\n");
719 sourcelist = thiscopy;
720 lastcopyentry = thiscopy;
721 } else if (concatnextfilename) {
722 WINE_TRACE("Adding to source file list to be concatenated\n");
723 lastcopyentry->next = thiscopy;
724 lastcopyentry = thiscopy;
725 } else if (destination == NULL) {
726 destination = thiscopy;
727 } else {
728 /* We have processed sources and destinations and still found more to do - invalid */
729 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
730 errorlevel = 1;
731 goto exitreturn;
733 concatnextfilename = FALSE;
735 /* We either need to process the rest of the parameter or move to the next */
736 if (*pos1 == '/' || *pos1 == '+') {
737 thisparam = pos1;
738 continue;
739 } else {
740 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
744 /* Ensure we have at least one source file */
745 if (!sourcelist) {
746 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
747 errorlevel = 1;
748 goto exitreturn;
751 /* Default whether automatic overwriting is on. If we are interactive then
752 we prompt by default, otherwise we overwrite by default
753 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
754 if (opt_noty) prompt = TRUE;
755 else if (opt_y) prompt = FALSE;
756 else {
757 /* By default, we will force the overwrite in batch mode and ask for
758 * confirmation in interactive mode. */
759 prompt = interactive;
760 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
761 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
762 * default behavior. */
763 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
764 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
765 if (!lstrcmpiW (copycmd, parmY))
766 prompt = FALSE;
767 else if (!lstrcmpiW (copycmd, parmNoY))
768 prompt = TRUE;
772 /* Calculate the destination now - if none supplied, its current dir +
773 filename of first file in list*/
774 if (destination == NULL) {
776 WINE_TRACE("No destination supplied, so need to calculate it\n");
777 strcpyW(destname, dotW);
778 strcatW(destname, slashW);
780 destination = heap_alloc(sizeof(COPY_FILES));
781 if (destination == NULL) goto exitreturn;
782 destination->concatenate = FALSE; /* Not used for destination */
783 destination->binarycopy = binarymode;
784 destination->next = NULL; /* Not used for destination */
785 destination->name = NULL; /* To be filled in */
786 destisdirectory = TRUE;
788 } else {
789 WCHAR *filenamepart;
790 DWORD attributes;
792 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
794 /* Convert to fully qualified path/filename */
795 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
796 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
798 /* If parameter is a directory, ensure it ends in \ */
799 attributes = GetFileAttributesW(destname);
800 if ((destname[strlenW(destname) - 1] == '\\') ||
801 ((attributes != INVALID_FILE_ATTRIBUTES) &&
802 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
804 destisdirectory = TRUE;
805 if (!(destname[strlenW(destname) - 1] == '\\')) strcatW(destname, slashW);
806 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
810 /* Normally, the destination is the current directory unless we are
811 concatenating, in which case its current directory plus first filename.
812 Note that if the
813 In addition by default it is a binary copy unless concatenating, when
814 the copy defaults to an ascii copy (stop at EOF). We do not know the
815 first source part yet (until we search) so flag as needing filling in. */
817 if (anyconcats) {
818 /* We have found an a+b type syntax, so destination has to be a filename
819 and we need to default to ascii copying. If we have been supplied a
820 directory as the destination, we need to defer calculating the name */
821 if (destisdirectory) appendfirstsource = TRUE;
822 if (destination->binarycopy == -1) destination->binarycopy = 0;
824 } else if (!destisdirectory) {
825 /* We have been asked to copy to a filename. Default to ascii IF the
826 source contains wildcards (true even if only one match) */
827 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
828 anyconcats = TRUE; /* We really are concatenating to a single file */
829 if (destination->binarycopy == -1) {
830 destination->binarycopy = 0;
832 } else {
833 if (destination->binarycopy == -1) {
834 destination->binarycopy = 1;
839 /* Save away the destination name*/
840 heap_free(destination->name);
841 destination->name = heap_strdupW(destname);
842 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
843 wine_dbgstr_w(destname), appendfirstsource);
845 /* Remember if the destination is a device */
846 if (strncmpW(destination->name, deviceW, strlenW(deviceW)) == 0) {
847 WINE_TRACE("Destination is a device\n");
848 dstisdevice = TRUE;
851 /* Now we need to walk the set of sources, and process each name we come to.
852 If anyconcats is true, we are writing to one file, otherwise we are using
853 the source name each time.
854 If destination exists, prompt for overwrite the first time (if concatenating
855 we ask each time until yes is answered)
856 The first source file we come across must exist (when wildcards expanded)
857 and if concatenating with overwrite prompts, each source file must exist
858 until a yes is answered. */
860 thiscopy = sourcelist;
861 prevcopy = NULL;
863 while (thiscopy != NULL) {
865 WCHAR srcpath[MAX_PATH];
866 const WCHAR *srcname;
867 WCHAR *filenamepart;
868 DWORD attributes;
869 BOOL srcisdevice = FALSE;
871 /* If it was not explicit, we now know whether we are concatenating or not and
872 hence whether to copy as binary or ascii */
873 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
875 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
876 to where the filename portion begins (used for wildcart expansion. */
877 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
878 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
880 /* If parameter is a directory, ensure it ends in \* */
881 attributes = GetFileAttributesW(srcpath);
882 if (srcpath[strlenW(srcpath) - 1] == '\\') {
884 /* We need to know where the filename part starts, so append * and
885 recalculate the full resulting path */
886 strcatW(thiscopy->name, starW);
887 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
888 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
890 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
891 (attributes != INVALID_FILE_ATTRIBUTES) &&
892 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
894 /* We need to know where the filename part starts, so append \* and
895 recalculate the full resulting path */
896 strcatW(thiscopy->name, slashstarW);
897 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
898 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
901 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
902 wine_dbgstr_w(srcpath), anyconcats);
904 /* If the source is a device, just use it, otherwise search */
905 if (strncmpW(srcpath, deviceW, strlenW(deviceW)) == 0) {
906 WINE_TRACE("Source is a device\n");
907 srcisdevice = TRUE;
908 srcname = &srcpath[4]; /* After the \\.\ prefix */
909 } else {
911 /* Loop through all source files */
912 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
913 hff = FindFirstFileW(srcpath, &fd);
914 if (hff != INVALID_HANDLE_VALUE) {
915 srcname = fd.cFileName;
919 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
920 do {
921 WCHAR outname[MAX_PATH];
922 BOOL overwrite;
924 /* Skip . and .., and directories */
925 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
926 WINE_TRACE("Skipping directories\n");
927 } else {
929 /* Build final destination name */
930 strcpyW(outname, destination->name);
931 if (destisdirectory || appendfirstsource) strcatW(outname, srcname);
933 /* Build source name */
934 if (!srcisdevice) strcpyW(filenamepart, srcname);
936 /* Do we just overwrite (we do if we are writing to a device) */
937 overwrite = !prompt;
938 if (dstisdevice || (anyconcats && writtenoneconcat)) {
939 overwrite = TRUE;
942 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
943 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
944 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
945 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
947 /* Prompt before overwriting */
948 if (!overwrite) {
949 DWORD attributes = GetFileAttributesW(outname);
950 if (attributes != INVALID_FILE_ATTRIBUTES) {
951 WCHAR* question;
952 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
953 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
954 LocalFree(question);
956 else overwrite = TRUE;
959 /* If we needed to save away the first filename, do it */
960 if (appendfirstsource && overwrite) {
961 heap_free(destination->name);
962 destination->name = heap_strdupW(outname);
963 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
964 appendfirstsource = FALSE;
965 destisdirectory = FALSE;
968 /* Do the copy as appropriate */
969 if (overwrite) {
970 if (anyconcats && writtenoneconcat) {
971 if (thiscopy->binarycopy) {
972 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
973 } else {
974 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
976 } else if (!thiscopy->binarycopy) {
977 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
978 } else if (srcisdevice) {
979 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
980 } else {
981 status = CopyFileW(srcpath, outname, FALSE);
983 if (!status) {
984 WCMD_print_error ();
985 errorlevel = 1;
986 } else {
987 WINE_TRACE("Copied successfully\n");
988 if (anyconcats) writtenoneconcat = TRUE;
990 /* Append EOF if ascii destination and we are not going to add more onto the end
991 Note: Testing shows windows has an optimization whereas if you have a binary
992 copy of a file to a single destination (ie concatenation) then it does not add
993 the EOF, hence the check on the source copy type below. */
994 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
995 if (!WCMD_AppendEOF(outname)) {
996 WCMD_print_error ();
997 errorlevel = 1;
1003 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1004 if (!srcisdevice) FindClose (hff);
1005 } else {
1006 /* Error if the first file was not found */
1007 if (!anyconcats || (anyconcats && !writtenoneconcat)) {
1008 WCMD_print_error ();
1009 errorlevel = 1;
1013 /* Step on to the next supplied source */
1014 thiscopy = thiscopy -> next;
1017 /* Append EOF if ascii destination and we were concatenating */
1018 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
1019 if (!WCMD_AppendEOF(destination->name)) {
1020 WCMD_print_error ();
1021 errorlevel = 1;
1025 /* Exit out of the routine, freeing any remaining allocated memory */
1026 exitreturn:
1028 thiscopy = sourcelist;
1029 while (thiscopy != NULL) {
1030 prevcopy = thiscopy;
1031 /* Free up this block*/
1032 thiscopy = thiscopy -> next;
1033 heap_free(prevcopy->name);
1034 heap_free(prevcopy);
1037 /* Free up the destination memory */
1038 if (destination) {
1039 heap_free(destination->name);
1040 heap_free(destination);
1043 return;
1046 /****************************************************************************
1047 * WCMD_create_dir
1049 * Create a directory (and, if needed, any intermediate directories).
1051 * Modifies its argument by replacing slashes temporarily with nulls.
1054 static BOOL create_full_path(WCHAR* path)
1056 WCHAR *p, *start;
1058 /* don't mess with drive letter portion of path, if any */
1059 start = path;
1060 if (path[1] == ':')
1061 start = path+2;
1063 /* Strip trailing slashes. */
1064 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
1065 *p = 0;
1067 /* Step through path, creating intermediate directories as needed. */
1068 /* First component includes drive letter, if any. */
1069 p = start;
1070 for (;;) {
1071 DWORD rv;
1072 /* Skip to end of component */
1073 while (*p == '\\') p++;
1074 while (*p && *p != '\\') p++;
1075 if (!*p) {
1076 /* path is now the original full path */
1077 return CreateDirectoryW(path, NULL);
1079 /* Truncate path, create intermediate directory, and restore path */
1080 *p = 0;
1081 rv = CreateDirectoryW(path, NULL);
1082 *p = '\\';
1083 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1084 return FALSE;
1086 /* notreached */
1087 return FALSE;
1090 void WCMD_create_dir (WCHAR *args) {
1091 int argno = 0;
1092 WCHAR *argN = args;
1094 if (param1[0] == 0x00) {
1095 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1096 return;
1098 /* Loop through all args */
1099 while (TRUE) {
1100 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1101 if (!argN) break;
1102 if (!create_full_path(thisArg)) {
1103 WCMD_print_error ();
1104 errorlevel = 1;
1109 /* Parse the /A options given by the user on the commandline
1110 * into a bitmask of wanted attributes (*wantSet),
1111 * and a bitmask of unwanted attributes (*wantClear).
1113 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1114 static const WCHAR parmA[] = {'/','A','\0'};
1115 WCHAR *p;
1117 /* both are strictly 'out' parameters */
1118 *wantSet=0;
1119 *wantClear=0;
1121 /* For each /A argument */
1122 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1123 /* Skip /A itself */
1124 p += 2;
1126 /* Skip optional : */
1127 if (*p == ':') p++;
1129 /* For each of the attribute specifier chars to this /A option */
1130 for (; *p != 0 && *p != '/'; p++) {
1131 BOOL negate = FALSE;
1132 DWORD mask = 0;
1134 if (*p == '-') {
1135 negate=TRUE;
1136 p++;
1139 /* Convert the attribute specifier to a bit in one of the masks */
1140 switch (*p) {
1141 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1142 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1143 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1144 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1145 default:
1146 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1148 if (negate)
1149 *wantClear |= mask;
1150 else
1151 *wantSet |= mask;
1156 /* If filename part of parameter is * or *.*,
1157 * and neither /Q nor /P options were given,
1158 * prompt the user whether to proceed.
1159 * Returns FALSE if user says no, TRUE otherwise.
1160 * *pPrompted is set to TRUE if the user is prompted.
1161 * (If /P supplied, del will prompt for individual files later.)
1163 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1164 static const WCHAR parmP[] = {'/','P','\0'};
1165 static const WCHAR parmQ[] = {'/','Q','\0'};
1167 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1168 static const WCHAR anyExt[]= {'.','*','\0'};
1169 WCHAR drive[10];
1170 WCHAR dir[MAX_PATH];
1171 WCHAR fname[MAX_PATH];
1172 WCHAR ext[MAX_PATH];
1173 WCHAR fpath[MAX_PATH];
1175 /* Convert path into actual directory spec */
1176 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1177 WCMD_splitpath(fpath, drive, dir, fname, ext);
1179 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1180 if ((strcmpW(fname, starW) == 0) &&
1181 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1183 WCHAR question[MAXSTRING];
1184 static const WCHAR fmt[] = {'%','s',' ','\0'};
1186 /* Caller uses this to suppress "file not found" warning later */
1187 *pPrompted = TRUE;
1189 /* Ask for confirmation */
1190 wsprintfW(question, fmt, fpath);
1191 return WCMD_ask_confirm(question, TRUE, NULL);
1194 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1195 return TRUE;
1198 /* Helper function for WCMD_delete().
1199 * Deletes a single file, directory, or wildcard.
1200 * If /S was given, does it recursively.
1201 * Returns TRUE if a file was deleted.
1203 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1205 static const WCHAR parmP[] = {'/','P','\0'};
1206 static const WCHAR parmS[] = {'/','S','\0'};
1207 static const WCHAR parmF[] = {'/','F','\0'};
1208 DWORD wanted_attrs;
1209 DWORD unwanted_attrs;
1210 BOOL found = FALSE;
1211 WCHAR argCopy[MAX_PATH];
1212 WIN32_FIND_DATAW fd;
1213 HANDLE hff;
1214 WCHAR fpath[MAX_PATH];
1215 WCHAR *p;
1216 BOOL handleParm = TRUE;
1218 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1220 strcpyW(argCopy, thisArg);
1221 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1222 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1224 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1225 /* Skip this arg if user declines to delete *.* */
1226 return FALSE;
1229 /* First, try to delete in the current directory */
1230 hff = FindFirstFileW(argCopy, &fd);
1231 if (hff == INVALID_HANDLE_VALUE) {
1232 handleParm = FALSE;
1233 } else {
1234 found = TRUE;
1237 /* Support del <dirname> by just deleting all files dirname\* */
1238 if (handleParm
1239 && (strchrW(argCopy,'*') == NULL)
1240 && (strchrW(argCopy,'?') == NULL)
1241 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1243 WCHAR modifiedParm[MAX_PATH];
1244 static const WCHAR slashStar[] = {'\\','*','\0'};
1246 strcpyW(modifiedParm, argCopy);
1247 strcatW(modifiedParm, slashStar);
1248 FindClose(hff);
1249 found = TRUE;
1250 WCMD_delete_one(modifiedParm);
1252 } else if (handleParm) {
1254 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1255 strcpyW (fpath, argCopy);
1256 do {
1257 p = strrchrW (fpath, '\\');
1258 if (p != NULL) {
1259 *++p = '\0';
1260 strcatW (fpath, fd.cFileName);
1262 else strcpyW (fpath, fd.cFileName);
1263 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1264 BOOL ok;
1266 /* Handle attribute matching (/A) */
1267 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1268 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1270 /* /P means prompt for each file */
1271 if (ok && strstrW (quals, parmP) != NULL) {
1272 WCHAR* question;
1274 /* Ask for confirmation */
1275 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1276 ok = WCMD_ask_confirm(question, FALSE, NULL);
1277 LocalFree(question);
1280 /* Only proceed if ok to */
1281 if (ok) {
1283 /* If file is read only, and /A:r or /F supplied, delete it */
1284 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1285 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1286 strstrW (quals, parmF) != NULL)) {
1287 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1290 /* Now do the delete */
1291 if (!DeleteFileW(fpath)) WCMD_print_error ();
1295 } while (FindNextFileW(hff, &fd) != 0);
1296 FindClose (hff);
1299 /* Now recurse into all subdirectories handling the parameter in the same way */
1300 if (strstrW (quals, parmS) != NULL) {
1302 WCHAR thisDir[MAX_PATH];
1303 int cPos;
1305 WCHAR drive[10];
1306 WCHAR dir[MAX_PATH];
1307 WCHAR fname[MAX_PATH];
1308 WCHAR ext[MAX_PATH];
1310 /* Convert path into actual directory spec */
1311 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1312 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1314 strcpyW(thisDir, drive);
1315 strcatW(thisDir, dir);
1316 cPos = strlenW(thisDir);
1318 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1320 /* Append '*' to the directory */
1321 thisDir[cPos] = '*';
1322 thisDir[cPos+1] = 0x00;
1324 hff = FindFirstFileW(thisDir, &fd);
1326 /* Remove residual '*' */
1327 thisDir[cPos] = 0x00;
1329 if (hff != INVALID_HANDLE_VALUE) {
1330 DIRECTORY_STACK *allDirs = NULL;
1331 DIRECTORY_STACK *lastEntry = NULL;
1333 do {
1334 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1335 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1336 (strcmpW(fd.cFileName, dotW) != 0)) {
1338 DIRECTORY_STACK *nextDir;
1339 WCHAR subParm[MAX_PATH];
1341 /* Work out search parameter in sub dir */
1342 strcpyW (subParm, thisDir);
1343 strcatW (subParm, fd.cFileName);
1344 strcatW (subParm, slashW);
1345 strcatW (subParm, fname);
1346 strcatW (subParm, ext);
1347 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1349 /* Allocate memory, add to list */
1350 nextDir = heap_alloc(sizeof(DIRECTORY_STACK));
1351 if (allDirs == NULL) allDirs = nextDir;
1352 if (lastEntry != NULL) lastEntry->next = nextDir;
1353 lastEntry = nextDir;
1354 nextDir->next = NULL;
1355 nextDir->dirName = heap_strdupW(subParm);
1357 } while (FindNextFileW(hff, &fd) != 0);
1358 FindClose (hff);
1360 /* Go through each subdir doing the delete */
1361 while (allDirs != NULL) {
1362 DIRECTORY_STACK *tempDir;
1364 tempDir = allDirs->next;
1365 found |= WCMD_delete_one (allDirs->dirName);
1367 heap_free(allDirs->dirName);
1368 heap_free(allDirs);
1369 allDirs = tempDir;
1374 return found;
1377 /****************************************************************************
1378 * WCMD_delete
1380 * Delete a file or wildcarded set.
1382 * Note on /A:
1383 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1384 * - Each set is a pattern, eg /ahr /as-r means
1385 * readonly+hidden OR nonreadonly system files
1386 * - The '-' applies to a single field, ie /a:-hr means read only
1387 * non-hidden files
1390 BOOL WCMD_delete (WCHAR *args) {
1391 int argno;
1392 WCHAR *argN;
1393 BOOL argsProcessed = FALSE;
1394 BOOL foundAny = FALSE;
1396 errorlevel = 0;
1398 for (argno=0; ; argno++) {
1399 BOOL found;
1400 WCHAR *thisArg;
1402 argN = NULL;
1403 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1404 if (!argN)
1405 break; /* no more parameters */
1406 if (argN[0] == '/')
1407 continue; /* skip options */
1409 argsProcessed = TRUE;
1410 found = WCMD_delete_one(thisArg);
1411 if (!found) {
1412 errorlevel = 1;
1413 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]==';')
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]!=';') {
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 = !processThese;
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, mimicing 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, its a range, so get the next one as well */
1794 if (*nextchar == '-') {
1795 nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1797 /* We want to return the lowest number that is higher than lasttoken
1798 but only if range is positive */
1799 if (nextnumber2 >= nextnumber1 &&
1800 lasttoken < nextnumber2) {
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];
2241 /* Now build the item to use / search for in the specified directory,
2242 as it is fully qualified in the /R case */
2243 if (dirsToWalk) {
2244 strcpyW(fullitem, dirsToWalk->dirName);
2245 strcatW(fullitem, slashW);
2246 strcatW(fullitem, item);
2247 } else {
2248 strcpyW(fullitem, item);
2251 if (strpbrkW (fullitem, wildcards)) {
2253 hff = FindFirstFileW(fullitem, &fd);
2254 if (hff != INVALID_HANDLE_VALUE) {
2255 do {
2256 BOOL isDirectory = FALSE;
2258 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2260 /* Handle as files or dirs appropriately, but ignore . and .. */
2261 if (isDirectory == expandDirs &&
2262 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2263 (strcmpW(fd.cFileName, dotW) != 0))
2265 thisCmdStart = cmdStart;
2266 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2268 if (doRecurse) {
2269 strcpyW(fullitem, dirsToWalk->dirName);
2270 strcatW(fullitem, slashW);
2271 strcatW(fullitem, fd.cFileName);
2272 } else {
2273 strcpyW(fullitem, fd.cFileName);
2275 doExecuted = TRUE;
2277 /* Save away any existing for variable context (e.g. nested for loops)
2278 and restore it after executing the body of this for loop */
2279 if (varidx >= 0) {
2280 oldvariablevalue = forloopcontext.variable[varidx];
2281 forloopcontext.variable[varidx] = fullitem;
2283 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2284 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2286 cmdEnd = thisCmdStart;
2288 } while (FindNextFileW(hff, &fd) != 0);
2289 FindClose (hff);
2291 } else {
2292 doExecuted = TRUE;
2294 /* Save away any existing for variable context (e.g. nested for loops)
2295 and restore it after executing the body of this for loop */
2296 if (varidx >= 0) {
2297 oldvariablevalue = forloopcontext.variable[varidx];
2298 forloopcontext.variable[varidx] = fullitem;
2300 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2301 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2303 cmdEnd = thisCmdStart;
2306 } else if (useNumbers) {
2307 /* Convert the first 3 numbers to signed longs and save */
2308 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2309 /* else ignore them! */
2311 /* Filesets - either a list of files, or a command to run and parse the output */
2312 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2313 (forf_usebackq && *itemStart != '\''))) {
2315 HANDLE input;
2316 WCHAR *itemparm;
2318 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2319 wine_dbgstr_w(item));
2321 /* If backquote or single quote, we need to launch that command
2322 and parse the results - use a temporary file */
2323 if ((forf_usebackq && *itemStart == '`') ||
2324 (!forf_usebackq && *itemStart == '\'')) {
2326 /* Use itemstart because the command is the whole set, not just the first token */
2327 itemparm = itemStart;
2328 } else {
2330 /* Use item because the file to process is just the first item in the set */
2331 itemparm = item;
2333 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2335 /* Process the input file */
2336 if (input == INVALID_HANDLE_VALUE) {
2337 WCMD_print_error ();
2338 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2339 errorlevel = 1;
2340 return; /* FOR loop aborts at first failure here */
2342 } else {
2344 /* Read line by line until end of file */
2345 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2346 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2347 &forf_skip, forf_eol, forf_delims, forf_tokens);
2348 buffer[0] = 0;
2350 CloseHandle (input);
2353 /* When we have processed the item as a whole command, abort future set processing */
2354 if (itemparm==itemStart) {
2355 thisSet = NULL;
2356 break;
2359 /* Filesets - A string literal */
2360 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2361 (forf_usebackq && *itemStart == '\''))) {
2363 /* Remove leading and trailing character, ready to parse with delims= delimiters
2364 Note that the last quote is removed from the set and the string terminates
2365 there to mimic windows */
2366 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2367 if (strend) {
2368 *strend = 0x00;
2369 itemStart++;
2372 /* Copy the item away from the global buffer used by WCMD_parameter */
2373 strcpyW(buffer, itemStart);
2374 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2375 &forf_skip, forf_eol, forf_delims, forf_tokens);
2377 /* Only one string can be supplied in the whole set, abort future set processing */
2378 thisSet = NULL;
2379 break;
2382 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2383 i++;
2386 /* Move onto the next set line */
2387 if (thisSet) thisSet = thisSet->nextcommand;
2390 /* If /L is provided, now run the for loop */
2391 if (useNumbers) {
2392 WCHAR thisNum[20];
2393 static const WCHAR fmt[] = {'%','d','\0'};
2395 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2396 numbers[0], numbers[2], numbers[1]);
2397 for (i=numbers[0];
2398 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2399 i=i + numbers[1]) {
2401 sprintfW(thisNum, fmt, i);
2402 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2404 thisCmdStart = cmdStart;
2405 doExecuted = TRUE;
2407 /* Save away any existing for variable context (e.g. nested for loops)
2408 and restore it after executing the body of this for loop */
2409 if (varidx >= 0) {
2410 oldvariablevalue = forloopcontext.variable[varidx];
2411 forloopcontext.variable[varidx] = thisNum;
2413 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2414 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2416 cmdEnd = thisCmdStart;
2419 /* If we are walking directories, move on to any which remain */
2420 if (dirsToWalk != NULL) {
2421 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2422 heap_free(dirsToWalk->dirName);
2423 heap_free(dirsToWalk);
2424 dirsToWalk = nextDir;
2425 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2426 wine_dbgstr_w(dirsToWalk->dirName));
2427 else WINE_TRACE("Finished all directories.\n");
2430 } while (dirsToWalk != NULL);
2432 /* Now skip over the do part if we did not perform the for loop so far.
2433 We store in cmdEnd the next command after the do block, but we only
2434 know this if something was run. If it has not been, we need to calculate
2435 it. */
2436 if (!doExecuted) {
2437 thisCmdStart = cmdStart;
2438 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2439 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2440 cmdEnd = thisCmdStart;
2443 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2444 all processing, OR it should be pointing to the end of && processing OR
2445 it should be pointing at the NULL end of bracket for the DO. The return
2446 value needs to be the NEXT command to execute, which it either is, or
2447 we need to step over the closing bracket */
2448 *cmdList = cmdEnd;
2449 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2452 /**************************************************************************
2453 * WCMD_give_help
2455 * Simple on-line help. Help text is stored in the resource file.
2458 void WCMD_give_help (const WCHAR *args)
2460 size_t i;
2462 args = WCMD_skip_leading_spaces((WCHAR*) args);
2463 if (strlenW(args) == 0) {
2464 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2466 else {
2467 /* Display help message for builtin commands */
2468 for (i=0; i<=WCMD_EXIT; i++) {
2469 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2470 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2471 WCMD_output_asis (WCMD_LoadMessage(i));
2472 return;
2475 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2476 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2477 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2478 args, -1, externals[i], -1) == CSTR_EQUAL) {
2479 WCHAR cmd[128];
2480 static const WCHAR helpW[] = {' ', '/','?','\0'};
2481 strcpyW(cmd, args);
2482 strcatW(cmd, helpW);
2483 WCMD_run_program(cmd, FALSE);
2484 return;
2487 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2489 return;
2492 /****************************************************************************
2493 * WCMD_go_to
2495 * Batch file jump instruction. Not the most efficient algorithm ;-)
2496 * Prints error message if the specified label cannot be found - the file pointer is
2497 * then at EOF, effectively stopping the batch file.
2498 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2501 void WCMD_goto (CMD_LIST **cmdList) {
2503 WCHAR string[MAX_PATH];
2504 WCHAR current[MAX_PATH];
2506 /* Do not process any more parts of a processed multipart or multilines command */
2507 if (cmdList) *cmdList = NULL;
2509 if (context != NULL) {
2510 WCHAR *paramStart = param1, *str;
2511 static const WCHAR eofW[] = {':','e','o','f','\0'};
2513 if (param1[0] == 0x00) {
2514 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2515 return;
2518 /* Handle special :EOF label */
2519 if (lstrcmpiW (eofW, param1) == 0) {
2520 context -> skip_rest = TRUE;
2521 return;
2524 /* Support goto :label as well as goto label */
2525 if (*paramStart == ':') paramStart++;
2527 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2528 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2529 str = string;
2530 while (isspaceW (*str)) str++;
2531 if (*str == ':') {
2532 DWORD index = 0;
2533 str++;
2534 while (((current[index] = str[index])) && (!isspaceW (current[index])))
2535 index++;
2537 /* ignore space at the end */
2538 current[index] = 0;
2539 if (lstrcmpiW (current, paramStart) == 0) return;
2542 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2544 return;
2547 /*****************************************************************************
2548 * WCMD_pushd
2550 * Push a directory onto the stack
2553 void WCMD_pushd (const WCHAR *args)
2555 struct env_stack *curdir;
2556 WCHAR *thisdir;
2557 static const WCHAR parmD[] = {'/','D','\0'};
2559 if (strchrW(args, '/') != NULL) {
2560 SetLastError(ERROR_INVALID_PARAMETER);
2561 WCMD_print_error();
2562 return;
2565 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2566 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2567 if( !curdir || !thisdir ) {
2568 LocalFree(curdir);
2569 LocalFree(thisdir);
2570 WINE_ERR ("out of memory\n");
2571 return;
2574 /* Change directory using CD code with /D parameter */
2575 strcpyW(quals, parmD);
2576 GetCurrentDirectoryW (1024, thisdir);
2577 errorlevel = 0;
2578 WCMD_setshow_default(args);
2579 if (errorlevel) {
2580 LocalFree(curdir);
2581 LocalFree(thisdir);
2582 return;
2583 } else {
2584 curdir -> next = pushd_directories;
2585 curdir -> strings = thisdir;
2586 if (pushd_directories == NULL) {
2587 curdir -> u.stackdepth = 1;
2588 } else {
2589 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2591 pushd_directories = curdir;
2596 /*****************************************************************************
2597 * WCMD_popd
2599 * Pop a directory from the stack
2602 void WCMD_popd (void) {
2603 struct env_stack *temp = pushd_directories;
2605 if (!pushd_directories)
2606 return;
2608 /* pop the old environment from the stack, and make it the current dir */
2609 pushd_directories = temp->next;
2610 SetCurrentDirectoryW(temp->strings);
2611 LocalFree (temp->strings);
2612 LocalFree (temp);
2615 /*******************************************************************
2616 * evaluate_if_comparison
2618 * Evaluates an "if" comparison operation
2620 * PARAMS
2621 * leftOperand [I] left operand, non NULL
2622 * operator [I] "if" binary comparison operator, non NULL
2623 * rightOperand [I] right operand, non NULL
2624 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2626 * RETURNS
2627 * Success: 1 if operator applied to the operands evaluates to TRUE
2628 * 0 if operator applied to the operands evaluates to FALSE
2629 * Failure: -1 if operator is not recognized
2631 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2632 const WCHAR *rightOperand, int caseInsensitive)
2634 WCHAR *endptr_leftOp, *endptr_rightOp;
2635 long int leftOperand_int, rightOperand_int;
2636 BOOL int_operands;
2637 static const WCHAR lssW[] = {'l','s','s','\0'};
2638 static const WCHAR leqW[] = {'l','e','q','\0'};
2639 static const WCHAR equW[] = {'e','q','u','\0'};
2640 static const WCHAR neqW[] = {'n','e','q','\0'};
2641 static const WCHAR geqW[] = {'g','e','q','\0'};
2642 static const WCHAR gtrW[] = {'g','t','r','\0'};
2644 /* == is a special case, as it always compares strings */
2645 if (!lstrcmpiW(operator, eqeqW))
2646 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2647 : lstrcmpW (leftOperand, rightOperand) == 0;
2649 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2650 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2651 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2652 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2654 /* Perform actual (integer or string) comparison */
2655 if (!lstrcmpiW(operator, lssW)) {
2656 if (int_operands)
2657 return leftOperand_int < rightOperand_int;
2658 else
2659 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2660 : lstrcmpW (leftOperand, rightOperand) < 0;
2663 if (!lstrcmpiW(operator, leqW)) {
2664 if (int_operands)
2665 return leftOperand_int <= rightOperand_int;
2666 else
2667 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2668 : lstrcmpW (leftOperand, rightOperand) <= 0;
2671 if (!lstrcmpiW(operator, equW)) {
2672 if (int_operands)
2673 return leftOperand_int == rightOperand_int;
2674 else
2675 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2676 : lstrcmpW (leftOperand, rightOperand) == 0;
2679 if (!lstrcmpiW(operator, neqW)) {
2680 if (int_operands)
2681 return leftOperand_int != rightOperand_int;
2682 else
2683 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2684 : lstrcmpW (leftOperand, rightOperand) != 0;
2687 if (!lstrcmpiW(operator, geqW)) {
2688 if (int_operands)
2689 return leftOperand_int >= rightOperand_int;
2690 else
2691 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2692 : lstrcmpW (leftOperand, rightOperand) >= 0;
2695 if (!lstrcmpiW(operator, gtrW)) {
2696 if (int_operands)
2697 return leftOperand_int > rightOperand_int;
2698 else
2699 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2700 : lstrcmpW (leftOperand, rightOperand) > 0;
2703 return -1;
2706 /****************************************************************************
2707 * WCMD_if
2709 * Batch file conditional.
2711 * On entry, cmdlist will point to command containing the IF, and optionally
2712 * the first command to execute (if brackets not found)
2713 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2714 * If ('s were found, execute all within that bracket
2715 * Command may optionally be followed by an ELSE - need to skip instructions
2716 * in the else using the same logic
2718 * FIXME: Much more syntax checking needed!
2720 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2722 int negate; /* Negate condition */
2723 int test; /* Condition evaluation result */
2724 WCHAR condition[MAX_PATH], *command;
2725 static const WCHAR notW[] = {'n','o','t','\0'};
2726 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2727 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2728 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2729 static const WCHAR parmI[] = {'/','I','\0'};
2730 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2732 negate = !lstrcmpiW(param1,notW);
2733 strcpyW(condition, (negate ? param2 : param1));
2734 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2736 if (!lstrcmpiW (condition, errlvlW)) {
2737 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2738 WCHAR *endptr;
2739 long int param_int = strtolW(param, &endptr, 10);
2740 if (*endptr) goto syntax_err;
2741 test = ((long int)errorlevel >= param_int);
2742 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2744 else if (!lstrcmpiW (condition, existW)) {
2745 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2746 != INVALID_FILE_ATTRIBUTES);
2747 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2749 else if (!lstrcmpiW (condition, defdW)) {
2750 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2751 NULL, 0) > 0);
2752 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2754 else { /* comparison operation */
2755 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2756 WCHAR *paramStart;
2758 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, &paramStart, TRUE, FALSE));
2759 if (!*leftOperand)
2760 goto syntax_err;
2762 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2763 p = paramStart + strlenW(leftOperand);
2764 while (*p == ' ' || *p == '\t')
2765 p++;
2767 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2768 strcpyW(operator, eqeqW);
2769 else {
2770 strcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2771 if (!*operator) goto syntax_err;
2773 p += strlenW(operator);
2775 strcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2776 if (!*rightOperand)
2777 goto syntax_err;
2779 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2780 if (test == -1)
2781 goto syntax_err;
2783 p = paramStart + strlenW(rightOperand);
2784 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2787 /* Process rest of IF statement which is on the same line
2788 Note: This may process all or some of the cmdList (eg a GOTO) */
2789 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2790 return;
2792 syntax_err:
2793 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2796 /****************************************************************************
2797 * WCMD_move
2799 * Move a file, directory tree or wildcarded set of files.
2802 void WCMD_move (void)
2804 int status;
2805 WIN32_FIND_DATAW fd;
2806 HANDLE hff;
2807 WCHAR input[MAX_PATH];
2808 WCHAR output[MAX_PATH];
2809 WCHAR drive[10];
2810 WCHAR dir[MAX_PATH];
2811 WCHAR fname[MAX_PATH];
2812 WCHAR ext[MAX_PATH];
2814 if (param1[0] == 0x00) {
2815 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2816 return;
2819 /* If no destination supplied, assume current directory */
2820 if (param2[0] == 0x00) {
2821 strcpyW(param2, dotW);
2824 /* If 2nd parm is directory, then use original filename */
2825 /* Convert partial path to full path */
2826 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2827 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2828 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2829 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2831 /* Split into components */
2832 WCMD_splitpath(input, drive, dir, fname, ext);
2834 hff = FindFirstFileW(input, &fd);
2835 if (hff == INVALID_HANDLE_VALUE)
2836 return;
2838 do {
2839 WCHAR dest[MAX_PATH];
2840 WCHAR src[MAX_PATH];
2841 DWORD attribs;
2842 BOOL ok = TRUE;
2844 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2846 /* Build src & dest name */
2847 strcpyW(src, drive);
2848 strcatW(src, dir);
2850 /* See if dest is an existing directory */
2851 attribs = GetFileAttributesW(output);
2852 if (attribs != INVALID_FILE_ATTRIBUTES &&
2853 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2854 strcpyW(dest, output);
2855 strcatW(dest, slashW);
2856 strcatW(dest, fd.cFileName);
2857 } else {
2858 strcpyW(dest, output);
2861 strcatW(src, fd.cFileName);
2863 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2864 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2866 /* If destination exists, prompt unless /Y supplied */
2867 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2868 BOOL force = FALSE;
2869 WCHAR copycmd[MAXSTRING];
2870 DWORD len;
2872 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2873 if (strstrW (quals, parmNoY))
2874 force = FALSE;
2875 else if (strstrW (quals, parmY))
2876 force = TRUE;
2877 else {
2878 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2879 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2880 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2881 && ! lstrcmpiW (copycmd, parmY));
2884 /* Prompt if overwriting */
2885 if (!force) {
2886 WCHAR* question;
2888 /* Ask for confirmation */
2889 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2890 ok = WCMD_ask_confirm(question, FALSE, NULL);
2891 LocalFree(question);
2893 /* So delete the destination prior to the move */
2894 if (ok) {
2895 if (!DeleteFileW(dest)) {
2896 WCMD_print_error ();
2897 errorlevel = 1;
2898 ok = FALSE;
2904 if (ok) {
2905 status = MoveFileW(src, dest);
2906 } else {
2907 status = 1; /* Anything other than 0 to prevent error msg below */
2910 if (!status) {
2911 WCMD_print_error ();
2912 errorlevel = 1;
2914 } while (FindNextFileW(hff, &fd) != 0);
2916 FindClose(hff);
2919 /****************************************************************************
2920 * WCMD_pause
2922 * Suspend execution of a batch script until a key is typed
2925 void WCMD_pause (void)
2927 DWORD oldmode;
2928 BOOL have_console;
2929 DWORD count;
2930 WCHAR key;
2931 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2933 have_console = GetConsoleMode(hIn, &oldmode);
2934 if (have_console)
2935 SetConsoleMode(hIn, 0);
2937 WCMD_output_asis(anykey);
2938 WCMD_ReadFile(hIn, &key, 1, &count);
2939 if (have_console)
2940 SetConsoleMode(hIn, oldmode);
2943 /****************************************************************************
2944 * WCMD_remove_dir
2946 * Delete a directory.
2949 void WCMD_remove_dir (WCHAR *args) {
2951 int argno = 0;
2952 int argsProcessed = 0;
2953 WCHAR *argN = args;
2954 static const WCHAR parmS[] = {'/','S','\0'};
2955 static const WCHAR parmQ[] = {'/','Q','\0'};
2957 /* Loop through all args */
2958 while (argN) {
2959 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2960 if (argN && argN[0] != '/') {
2961 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2962 wine_dbgstr_w(quals));
2963 argsProcessed++;
2965 /* If subdirectory search not supplied, just try to remove
2966 and report error if it fails (eg if it contains a file) */
2967 if (strstrW (quals, parmS) == NULL) {
2968 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2970 /* Otherwise use ShFileOp to recursively remove a directory */
2971 } else {
2973 SHFILEOPSTRUCTW lpDir;
2975 /* Ask first */
2976 if (strstrW (quals, parmQ) == NULL) {
2977 BOOL ok;
2978 WCHAR question[MAXSTRING];
2979 static const WCHAR fmt[] = {'%','s',' ','\0'};
2981 /* Ask for confirmation */
2982 wsprintfW(question, fmt, thisArg);
2983 ok = WCMD_ask_confirm(question, TRUE, NULL);
2985 /* Abort if answer is 'N' */
2986 if (!ok) return;
2989 /* Do the delete */
2990 lpDir.hwnd = NULL;
2991 lpDir.pTo = NULL;
2992 lpDir.pFrom = thisArg;
2993 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2994 lpDir.wFunc = FO_DELETE;
2996 /* SHFileOperationW needs file list with a double null termination */
2997 thisArg[lstrlenW(thisArg) + 1] = 0x00;
2999 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3004 /* Handle no valid args */
3005 if (argsProcessed == 0) {
3006 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3007 return;
3012 /****************************************************************************
3013 * WCMD_rename
3015 * Rename a file.
3018 void WCMD_rename (void)
3020 int status;
3021 HANDLE hff;
3022 WIN32_FIND_DATAW fd;
3023 WCHAR input[MAX_PATH];
3024 WCHAR *dotDst = NULL;
3025 WCHAR drive[10];
3026 WCHAR dir[MAX_PATH];
3027 WCHAR fname[MAX_PATH];
3028 WCHAR ext[MAX_PATH];
3030 errorlevel = 0;
3032 /* Must be at least two args */
3033 if (param1[0] == 0x00 || param2[0] == 0x00) {
3034 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3035 errorlevel = 1;
3036 return;
3039 /* Destination cannot contain a drive letter or directory separator */
3040 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
3041 SetLastError(ERROR_INVALID_PARAMETER);
3042 WCMD_print_error();
3043 errorlevel = 1;
3044 return;
3047 /* Convert partial path to full path */
3048 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
3049 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3050 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3051 dotDst = strchrW(param2, '.');
3053 /* Split into components */
3054 WCMD_splitpath(input, drive, dir, fname, ext);
3056 hff = FindFirstFileW(input, &fd);
3057 if (hff == INVALID_HANDLE_VALUE)
3058 return;
3060 do {
3061 WCHAR dest[MAX_PATH];
3062 WCHAR src[MAX_PATH];
3063 WCHAR *dotSrc = NULL;
3064 int dirLen;
3066 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3068 /* FIXME: If dest name or extension is *, replace with filename/ext
3069 part otherwise use supplied name. This supports:
3070 ren *.fred *.jim
3071 ren jim.* fred.* etc
3072 However, windows has a more complex algorithm supporting eg
3073 ?'s and *'s mid name */
3074 dotSrc = strchrW(fd.cFileName, '.');
3076 /* Build src & dest name */
3077 strcpyW(src, drive);
3078 strcatW(src, dir);
3079 strcpyW(dest, src);
3080 dirLen = strlenW(src);
3081 strcatW(src, fd.cFileName);
3083 /* Build name */
3084 if (param2[0] == '*') {
3085 strcatW(dest, fd.cFileName);
3086 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3087 } else {
3088 strcatW(dest, param2);
3089 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3092 /* Build Extension */
3093 if (dotDst && (*(dotDst+1)=='*')) {
3094 if (dotSrc) strcatW(dest, dotSrc);
3095 } else if (dotDst) {
3096 if (dotDst) strcatW(dest, dotDst);
3099 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3100 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3102 status = MoveFileW(src, dest);
3104 if (!status) {
3105 WCMD_print_error ();
3106 errorlevel = 1;
3108 } while (FindNextFileW(hff, &fd) != 0);
3110 FindClose(hff);
3113 /*****************************************************************************
3114 * WCMD_dupenv
3116 * Make a copy of the environment.
3118 static WCHAR *WCMD_dupenv( const WCHAR *env )
3120 WCHAR *env_copy;
3121 int len;
3123 if( !env )
3124 return NULL;
3126 len = 0;
3127 while ( env[len] )
3128 len += (strlenW(&env[len]) + 1);
3130 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3131 if (!env_copy)
3133 WINE_ERR("out of memory\n");
3134 return env_copy;
3136 memcpy (env_copy, env, len*sizeof (WCHAR));
3137 env_copy[len] = 0;
3139 return env_copy;
3142 /*****************************************************************************
3143 * WCMD_setlocal
3145 * setlocal pushes the environment onto a stack
3146 * Save the environment as unicode so we don't screw anything up.
3148 void WCMD_setlocal (const WCHAR *s) {
3149 WCHAR *env;
3150 struct env_stack *env_copy;
3151 WCHAR cwd[MAX_PATH];
3152 BOOL newdelay;
3153 static const WCHAR ondelayW[] = {'E','N','A','B','L','E','D','E','L','A',
3154 'Y','E','D','E','X','P','A','N','S','I',
3155 'O','N','\0'};
3156 static const WCHAR offdelayW[] = {'D','I','S','A','B','L','E','D','E','L',
3157 'A','Y','E','D','E','X','P','A','N','S',
3158 'I','O','N','\0'};
3160 /* setlocal does nothing outside of batch programs */
3161 if (!context) return;
3163 /* DISABLEEXTENSIONS ignored */
3165 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3166 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3167 if (!strcmpiW(param1, ondelayW) || !strcmpiW(param2, ondelayW)) {
3168 newdelay = TRUE;
3169 } else if (!strcmpiW(param1, offdelayW) || !strcmpiW(param2, offdelayW)) {
3170 newdelay = FALSE;
3171 } else {
3172 newdelay = delayedsubst;
3174 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3176 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3177 if( !env_copy )
3179 WINE_ERR ("out of memory\n");
3180 return;
3183 env = GetEnvironmentStringsW ();
3184 env_copy->strings = WCMD_dupenv (env);
3185 if (env_copy->strings)
3187 env_copy->batchhandle = context->h;
3188 env_copy->next = saved_environment;
3189 env_copy->delayedsubst = delayedsubst;
3190 delayedsubst = newdelay;
3191 saved_environment = env_copy;
3193 /* Save the current drive letter */
3194 GetCurrentDirectoryW(MAX_PATH, cwd);
3195 env_copy->u.cwd = cwd[0];
3197 else
3198 LocalFree (env_copy);
3200 FreeEnvironmentStringsW (env);
3204 /*****************************************************************************
3205 * WCMD_endlocal
3207 * endlocal pops the environment off a stack
3208 * Note: When searching for '=', search from WCHAR position 1, to handle
3209 * special internal environment variables =C:, =D: etc
3211 void WCMD_endlocal (void) {
3212 WCHAR *env, *old, *p;
3213 struct env_stack *temp;
3214 int len, n;
3216 /* setlocal does nothing outside of batch programs */
3217 if (!context) return;
3219 /* setlocal needs a saved environment from within the same context (batch
3220 program) as it was saved in */
3221 if (!saved_environment || saved_environment->batchhandle != context->h)
3222 return;
3224 /* pop the old environment from the stack */
3225 temp = saved_environment;
3226 saved_environment = temp->next;
3228 /* delete the current environment, totally */
3229 env = GetEnvironmentStringsW ();
3230 old = WCMD_dupenv (env);
3231 len = 0;
3232 while (old[len]) {
3233 n = strlenW(&old[len]) + 1;
3234 p = strchrW(&old[len] + 1, '=');
3235 if (p)
3237 *p++ = 0;
3238 SetEnvironmentVariableW (&old[len], NULL);
3240 len += n;
3242 LocalFree (old);
3243 FreeEnvironmentStringsW (env);
3245 /* restore old environment */
3246 env = temp->strings;
3247 len = 0;
3248 delayedsubst = temp->delayedsubst;
3249 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3250 while (env[len]) {
3251 n = strlenW(&env[len]) + 1;
3252 p = strchrW(&env[len] + 1, '=');
3253 if (p)
3255 *p++ = 0;
3256 SetEnvironmentVariableW (&env[len], p);
3258 len += n;
3261 /* Restore current drive letter */
3262 if (IsCharAlphaW(temp->u.cwd)) {
3263 WCHAR envvar[4];
3264 WCHAR cwd[MAX_PATH];
3265 static const WCHAR fmt[] = {'=','%','c',':','\0'};
3267 wsprintfW(envvar, fmt, temp->u.cwd);
3268 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3269 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3270 SetCurrentDirectoryW(cwd);
3274 LocalFree (env);
3275 LocalFree (temp);
3278 /*****************************************************************************
3279 * WCMD_setshow_default
3281 * Set/Show the current default directory
3284 void WCMD_setshow_default (const WCHAR *args) {
3286 BOOL status;
3287 WCHAR string[1024];
3288 WCHAR cwd[1024];
3289 WCHAR *pos;
3290 WIN32_FIND_DATAW fd;
3291 HANDLE hff;
3292 static const WCHAR parmD[] = {'/','D','\0'};
3294 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3296 /* Skip /D and trailing whitespace if on the front of the command line */
3297 if (CompareStringW(LOCALE_USER_DEFAULT,
3298 NORM_IGNORECASE | SORT_STRINGSORT,
3299 args, 2, parmD, -1) == CSTR_EQUAL) {
3300 args += 2;
3301 while (*args && (*args==' ' || *args=='\t'))
3302 args++;
3305 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3306 if (strlenW(args) == 0) {
3307 strcatW (cwd, newlineW);
3308 WCMD_output_asis (cwd);
3310 else {
3311 /* Remove any double quotes, which may be in the
3312 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3313 pos = string;
3314 while (*args) {
3315 if (*args != '"') *pos++ = *args;
3316 args++;
3318 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3319 pos--;
3320 *pos = 0x00;
3322 /* Search for appropriate directory */
3323 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3324 hff = FindFirstFileW(string, &fd);
3325 if (hff != INVALID_HANDLE_VALUE) {
3326 do {
3327 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3328 WCHAR fpath[MAX_PATH];
3329 WCHAR drive[10];
3330 WCHAR dir[MAX_PATH];
3331 WCHAR fname[MAX_PATH];
3332 WCHAR ext[MAX_PATH];
3333 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3335 /* Convert path into actual directory spec */
3336 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3337 WCMD_splitpath(fpath, drive, dir, fname, ext);
3339 /* Rebuild path */
3340 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3341 break;
3343 } while (FindNextFileW(hff, &fd) != 0);
3344 FindClose(hff);
3347 /* Change to that directory */
3348 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3350 status = SetCurrentDirectoryW(string);
3351 if (!status) {
3352 errorlevel = 1;
3353 WCMD_print_error ();
3354 return;
3355 } else {
3357 /* Save away the actual new directory, to store as current location */
3358 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3360 /* Restore old directory if drive letter would change, and
3361 CD x:\directory /D (or pushd c:\directory) not supplied */
3362 if ((strstrW(quals, parmD) == NULL) &&
3363 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3364 SetCurrentDirectoryW(cwd);
3368 /* Set special =C: type environment variable, for drive letter of
3369 change of directory, even if path was restored due to missing
3370 /D (allows changing drive letter when not resident on that
3371 drive */
3372 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3373 WCHAR env[4];
3374 strcpyW(env, equalW);
3375 memcpy(env+1, string, 2 * sizeof(WCHAR));
3376 env[3] = 0x00;
3377 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3378 SetEnvironmentVariableW(env, string);
3382 return;
3385 /****************************************************************************
3386 * WCMD_setshow_date
3388 * Set/Show the system date
3389 * FIXME: Can't change date yet
3392 void WCMD_setshow_date (void) {
3394 WCHAR curdate[64], buffer[64];
3395 DWORD count;
3396 static const WCHAR parmT[] = {'/','T','\0'};
3398 if (strlenW(param1) == 0) {
3399 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3400 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3401 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3402 if (strstrW (quals, parmT) == NULL) {
3403 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3404 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3405 if (count > 2) {
3406 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3410 else WCMD_print_error ();
3412 else {
3413 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3417 /****************************************************************************
3418 * WCMD_compare
3419 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3420 * the equals sign.
3422 static int WCMD_compare( const void *a, const void *b )
3424 int r;
3425 const WCHAR * const *str_a = a, * const *str_b = b;
3426 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3427 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3428 if( r == CSTR_LESS_THAN ) return -1;
3429 if( r == CSTR_GREATER_THAN ) return 1;
3430 return 0;
3433 /****************************************************************************
3434 * WCMD_setshow_sortenv
3436 * sort variables into order for display
3437 * Optionally only display those who start with a stub
3438 * returns the count displayed
3440 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3442 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3443 const WCHAR **str;
3445 if (stub) stublen = strlenW(stub);
3447 /* count the number of strings, and the total length */
3448 while ( s[len] ) {
3449 len += (strlenW(&s[len]) + 1);
3450 count++;
3453 /* add the strings to an array */
3454 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3455 if( !str )
3456 return 0;
3457 str[0] = s;
3458 for( i=1; i<count; i++ )
3459 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3461 /* sort the array */
3462 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3464 /* print it */
3465 for( i=0; i<count; i++ ) {
3466 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3467 NORM_IGNORECASE | SORT_STRINGSORT,
3468 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3469 /* Don't display special internal variables */
3470 if (str[i][0] != '=') {
3471 WCMD_output_asis(str[i]);
3472 WCMD_output_asis(newlineW);
3473 displayedcount++;
3478 LocalFree( str );
3479 return displayedcount;
3482 /****************************************************************************
3483 * WCMD_getprecedence
3484 * Return the precedence of a particular operator
3486 static int WCMD_getprecedence(const WCHAR in)
3488 switch (in) {
3489 case '!':
3490 case '~':
3491 case OP_POSITIVE:
3492 case OP_NEGATIVE:
3493 return 8;
3494 case '*':
3495 case '/':
3496 case '%':
3497 return 7;
3498 case '+':
3499 case '-':
3500 return 6;
3501 case '<':
3502 case '>':
3503 return 5;
3504 case '&':
3505 return 4;
3506 case '^':
3507 return 3;
3508 case '|':
3509 return 2;
3510 case '=':
3511 case OP_ASSSIGNMUL:
3512 case OP_ASSSIGNDIV:
3513 case OP_ASSSIGNMOD:
3514 case OP_ASSSIGNADD:
3515 case OP_ASSSIGNSUB:
3516 case OP_ASSSIGNAND:
3517 case OP_ASSSIGNNOT:
3518 case OP_ASSSIGNOR:
3519 case OP_ASSSIGNSHL:
3520 case OP_ASSSIGNSHR:
3521 return 1;
3522 default:
3523 return 0;
3527 /****************************************************************************
3528 * WCMD_pushnumber
3529 * Push either a number or name (environment variable) onto the supplied
3530 * stack
3532 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3533 VARSTACK *thisstack = heap_alloc(sizeof(VARSTACK));
3534 thisstack->isnum = (var == NULL);
3535 if (var) {
3536 thisstack->variable = var;
3537 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3538 } else {
3539 thisstack->value = num;
3540 WINE_TRACE("Pushed number %d\n", num);
3542 thisstack->next = *varstack;
3543 *varstack = thisstack;
3546 /****************************************************************************
3547 * WCMD_peeknumber
3548 * Returns the value of the top number or environment variable on the stack
3549 * and leaves the item on the stack.
3551 static int WCMD_peeknumber(VARSTACK **varstack) {
3552 int result = 0;
3553 VARSTACK *thisvar;
3555 if (varstack) {
3556 thisvar = *varstack;
3557 if (!thisvar->isnum) {
3558 WCHAR tmpstr[MAXSTRING];
3559 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3560 result = strtoulW(tmpstr,NULL,0);
3562 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3563 } else {
3564 result = thisvar->value;
3567 WINE_TRACE("Peeked number %d\n", result);
3568 return result;
3571 /****************************************************************************
3572 * WCMD_popnumber
3573 * Returns the value of the top number or environment variable on the stack
3574 * and removes the item from the stack.
3576 static int WCMD_popnumber(VARSTACK **varstack) {
3577 int result = 0;
3578 VARSTACK *thisvar;
3580 if (varstack) {
3581 thisvar = *varstack;
3582 result = WCMD_peeknumber(varstack);
3583 if (!thisvar->isnum) heap_free(thisvar->variable);
3584 *varstack = thisvar->next;
3585 heap_free(thisvar);
3587 WINE_TRACE("Popped number %d\n", result);
3588 return result;
3591 /****************************************************************************
3592 * WCMD_pushoperator
3593 * Push an operator onto the supplied stack
3595 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3596 OPSTACK *thisstack = heap_alloc(sizeof(OPSTACK));
3597 thisstack->precedence = precedence;
3598 thisstack->op = op;
3599 thisstack->next = *opstack;
3600 WINE_TRACE("Pushed operator %c\n", op);
3601 *opstack = thisstack;
3604 /****************************************************************************
3605 * WCMD_popoperator
3606 * Returns the operator from the top of the stack and removes the item from
3607 * the stack.
3609 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3610 WCHAR result = 0;
3611 OPSTACK *thisop;
3613 if (opstack) {
3614 thisop = *opstack;
3615 result = thisop->op;
3616 *opstack = thisop->next;
3617 heap_free(thisop);
3619 WINE_TRACE("Popped operator %c\n", result);
3620 return result;
3623 /****************************************************************************
3624 * WCMD_reduce
3625 * Actions the top operator on the stack against the first and sometimes
3626 * second value on the variable stack, and pushes the result
3627 * Returns non-zero on error.
3629 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3630 OPSTACK *thisop;
3631 int var1,var2;
3632 int rc = 0;
3634 if (!*opstack || !*varstack) {
3635 WINE_TRACE("No operators for the reduce\n");
3636 return WCMD_NOOPERATOR;
3639 /* Remove the top operator */
3640 thisop = *opstack;
3641 *opstack = (*opstack)->next;
3642 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop->op);
3644 /* One variable operators */
3645 var1 = WCMD_popnumber(varstack);
3646 switch (thisop->op) {
3647 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3648 break;
3649 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3650 break;
3651 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3652 break;
3653 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3654 break;
3657 /* Two variable operators */
3658 if (!*varstack) {
3659 WINE_TRACE("No operands left for the reduce?\n");
3660 return WCMD_NOOPERAND;
3662 switch (thisop->op) {
3663 case '!':
3664 case '~':
3665 case OP_POSITIVE:
3666 case OP_NEGATIVE:
3667 break; /* Handled above */
3668 case '*': var2 = WCMD_popnumber(varstack);
3669 WCMD_pushnumber(NULL, var2*var1, varstack);
3670 break;
3671 case '/': var2 = WCMD_popnumber(varstack);
3672 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3673 WCMD_pushnumber(NULL, var2/var1, varstack);
3674 break;
3675 case '+': var2 = WCMD_popnumber(varstack);
3676 WCMD_pushnumber(NULL, var2+var1, varstack);
3677 break;
3678 case '-': var2 = WCMD_popnumber(varstack);
3679 WCMD_pushnumber(NULL, var2-var1, varstack);
3680 break;
3681 case '&': var2 = WCMD_popnumber(varstack);
3682 WCMD_pushnumber(NULL, var2&var1, varstack);
3683 break;
3684 case '%': var2 = WCMD_popnumber(varstack);
3685 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3686 WCMD_pushnumber(NULL, var2%var1, varstack);
3687 break;
3688 case '^': var2 = WCMD_popnumber(varstack);
3689 WCMD_pushnumber(NULL, var2^var1, varstack);
3690 break;
3691 case '<': var2 = WCMD_popnumber(varstack);
3692 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3693 which differs from the compiler (for example gcc) so being explicit. */
3694 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3695 WCMD_pushnumber(NULL, 0, varstack);
3696 } else {
3697 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 WCMD_pushnumber(NULL, var2|var1, varstack);
3705 break;
3707 case OP_ASSSIGNMUL:
3708 case OP_ASSSIGNDIV:
3709 case OP_ASSSIGNMOD:
3710 case OP_ASSSIGNADD:
3711 case OP_ASSSIGNSUB:
3712 case OP_ASSSIGNAND:
3713 case OP_ASSSIGNNOT:
3714 case OP_ASSSIGNOR:
3715 case OP_ASSSIGNSHL:
3716 case OP_ASSSIGNSHR:
3718 int i = 0;
3720 /* The left of an equals must be one variable */
3721 if (!(*varstack) || (*varstack)->isnum) {
3722 return WCMD_NOOPERAND;
3725 /* Make the number stack grow by inserting the value of the variable */
3726 var2 = WCMD_peeknumber(varstack);
3727 WCMD_pushnumber(NULL, var2, varstack);
3728 WCMD_pushnumber(NULL, var1, varstack);
3730 /* Make the operand stack grow by pushing the assign operator plus the
3731 operator to perform */
3732 while (calcassignments[i].op != ' ' &&
3733 calcassignments[i].calculatedop != thisop->op) {
3734 i++;
3736 if (calcassignments[i].calculatedop == ' ') {
3737 WINE_ERR("Unexpected operator %c\n", thisop->op);
3738 return WCMD_NOOPERATOR;
3740 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3741 WCMD_pushoperator(calcassignments[i].op,
3742 WCMD_getprecedence(calcassignments[i].op), opstack);
3743 break;
3746 case '=':
3748 WCHAR intFormat[] = {'%','d','\0'};
3749 WCHAR result[MAXSTRING];
3751 /* Build the result, then push it onto the stack */
3752 sprintfW(result, intFormat, var1);
3753 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3754 wine_dbgstr_w(result));
3755 SetEnvironmentVariableW((*varstack)->variable, result);
3756 var2 = WCMD_popnumber(varstack);
3757 WCMD_pushnumber(NULL, var1, varstack);
3758 break;
3761 default: WINE_ERR("Unrecognized operator %c\n", thisop->op);
3764 heap_free(thisop);
3765 return rc;
3769 /****************************************************************************
3770 * WCMD_handleExpression
3771 * Handles an expression provided to set /a - If it finds brackets, it uses
3772 * recursion to process the parts in brackets.
3774 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3776 static const WCHAR mathDelims[] = {' ','\t','(',')','!','~','-','*','/','%',
3777 '+','<','>','&','^','|','=',',','\0' };
3778 int rc = 0;
3779 WCHAR *pos;
3780 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3781 OPSTACK *opstackhead = NULL;
3782 VARSTACK *varstackhead = NULL;
3783 WCHAR foundhalf = 0;
3785 /* Initialize */
3786 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3787 pos = *expr;
3789 /* Iterate through until whole expression is processed */
3790 while (pos && *pos) {
3791 BOOL treatasnumber;
3793 /* Skip whitespace to get to the next character to process*/
3794 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3795 if (!*pos) goto exprreturn;
3797 /* If we have found anything other than an operator then its a number/variable */
3798 if (strchrW(mathDelims, *pos) == NULL) {
3799 WCHAR *parmstart, *parm, *dupparm;
3800 WCHAR *nextpos;
3802 /* Cannot have an expression with var/number twice, without an operator
3803 in-between, nor or number following a half constructed << or >> operator */
3804 if (lastwasnumber || foundhalf) {
3805 rc = WCMD_NOOPERATOR;
3806 goto exprerrorreturn;
3808 lastwasnumber = TRUE;
3810 if (isdigitW(*pos)) {
3811 /* For a number - just push it onto the stack */
3812 int num = strtoulW(pos, &nextpos, 0);
3813 WCMD_pushnumber(NULL, num, &varstackhead);
3814 pos = nextpos;
3816 /* Verify the number was validly formed */
3817 if (*nextpos && (strchrW(mathDelims, *nextpos) == NULL)) {
3818 rc = WCMD_BADHEXOCT;
3819 goto exprerrorreturn;
3821 } else {
3823 /* For a variable - just push it onto the stack */
3824 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3825 dupparm = heap_strdupW(parm);
3826 WCMD_pushnumber(dupparm, 0, &varstackhead);
3827 pos = parmstart + strlenW(dupparm);
3829 continue;
3832 /* We have found an operator. Some operators are one character, some two, and the minus
3833 and plus signs need special processing as they can be either operators or just influence
3834 the parameter which follows them */
3835 if (foundhalf && (*pos != foundhalf)) {
3836 /* Badly constructed operator pair */
3837 rc = WCMD_NOOPERATOR;
3838 goto exprerrorreturn;
3841 treatasnumber = FALSE; /* We are processing an operand */
3842 switch (*pos) {
3844 /* > and < are special as they are double character operators (and spaces can be between them!)
3845 If we see these for the first time, set a flag, and second time around we continue.
3846 Note these double character operators are stored as just one of the characters on the stack */
3847 case '>':
3848 case '<': if (!foundhalf) {
3849 foundhalf = *pos;
3850 pos++;
3851 break;
3853 /* We have found the rest, so clear up the knowledge of the half completed part and
3854 drop through to normal operator processing */
3855 foundhalf = 0;
3856 /* drop through */
3858 case '=': if (*pos=='=') {
3859 /* = is special cased as if the last was an operator then we may have e.g. += or
3860 *= etc which we need to handle by replacing the operator that is on the stack
3861 with a calculated assignment equivalent */
3862 if (!lastwasnumber && opstackhead) {
3863 int i = 0;
3864 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3865 i++;
3867 if (calcassignments[i].op == ' ') {
3868 rc = WCMD_NOOPERAND;
3869 goto exprerrorreturn;
3870 } else {
3871 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3872 when the general operator handling happens further down. */
3873 *pos = calcassignments[i].calculatedop;
3874 WCMD_popoperator(&opstackhead);
3878 /* Drop though */
3880 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3881 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3882 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
3883 /* drop through */
3884 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
3885 /* drop through */
3887 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3888 case '!': /* drop through */
3889 case '~': /* drop through */
3890 case '/': /* drop through */
3891 case '%': /* drop through */
3892 case '&': /* drop through */
3893 case '^': /* drop through */
3894 case '*': /* drop through */
3895 case '|':
3896 /* General code for handling most of the operators - look at the
3897 precedence of the top item on the stack, and see if we need to
3898 action the stack before we push something else onto it. */
3900 int precedence = WCMD_getprecedence(*pos);
3901 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
3902 precedence, !opstackhead?-1:opstackhead->precedence);
3904 /* In general, for things with the same precedence, reduce immediately
3905 except for assignments and unary operators which do not */
3906 while (!rc && opstackhead &&
3907 ((opstackhead->precedence > precedence) ||
3908 ((opstackhead->precedence == precedence) &&
3909 (precedence != 1) && (precedence != 8)))) {
3910 rc = WCMD_reduce(&opstackhead, &varstackhead);
3912 if (rc) goto exprerrorreturn;
3913 WCMD_pushoperator(*pos, precedence, &opstackhead);
3914 pos++;
3915 break;
3918 /* comma means start a new expression, ie calculate what we have */
3919 case ',':
3921 int prevresult = -1;
3922 WINE_TRACE("Found expression delimiter - reducing exising stacks\n");
3923 while (!rc && opstackhead) {
3924 rc = WCMD_reduce(&opstackhead, &varstackhead);
3926 if (rc) goto exprerrorreturn;
3927 /* If we have anything other than one number left, error
3928 otherwise throw the number away */
3929 if (!varstackhead || varstackhead->next) {
3930 rc = WCMD_NOOPERATOR;
3931 goto exprerrorreturn;
3933 prevresult = WCMD_popnumber(&varstackhead);
3934 WINE_TRACE("Expression resolved to %d\n", prevresult);
3935 heap_free(varstackhead);
3936 varstackhead = NULL;
3937 pos++;
3938 break;
3941 /* Open bracket - use iteration to parse the inner expression, then continue */
3942 case '(' : {
3943 int exprresult = 0;
3944 pos++;
3945 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
3946 if (rc) goto exprerrorreturn;
3947 WCMD_pushnumber(NULL, exprresult, &varstackhead);
3948 break;
3951 /* Close bracket - we have finished this depth, calculate and return */
3952 case ')' : {
3953 pos++;
3954 treatasnumber = TRUE; /* Things in brackets result in a number */
3955 if (depth == 0) {
3956 rc = WCMD_BADPAREN;
3957 goto exprerrorreturn;
3959 goto exprreturn;
3962 default:
3963 WINE_ERR("Unrecognized operator %c\n", *pos);
3964 pos++;
3966 lastwasnumber = treatasnumber;
3969 exprreturn:
3970 *expr = pos;
3972 /* We need to reduce until we have a single number (or variable) on the
3973 stack and set the return value to that */
3974 while (!rc && opstackhead) {
3975 rc = WCMD_reduce(&opstackhead, &varstackhead);
3977 if (rc) goto exprerrorreturn;
3979 /* If we have anything other than one number left, error
3980 otherwise throw the number away */
3981 if (!varstackhead || varstackhead->next) {
3982 rc = WCMD_NOOPERATOR;
3983 goto exprerrorreturn;
3986 /* Now get the number (and convert if its just a variable name) */
3987 *ret = WCMD_popnumber(&varstackhead);
3989 exprerrorreturn:
3990 /* Free all remaining memory */
3991 while (opstackhead) WCMD_popoperator(&opstackhead);
3992 while (varstackhead) WCMD_popnumber(&varstackhead);
3994 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
3995 return rc;
3998 /****************************************************************************
3999 * WCMD_setshow_env
4001 * Set/Show the environment variables
4004 void WCMD_setshow_env (WCHAR *s) {
4006 LPVOID env;
4007 WCHAR *p;
4008 int status;
4009 static const WCHAR parmP[] = {'/','P','\0'};
4010 static const WCHAR parmA[] = {'/','A','\0'};
4011 WCHAR string[MAXSTRING];
4013 if (param1[0] == 0x00 && quals[0] == 0x00) {
4014 env = GetEnvironmentStringsW();
4015 WCMD_setshow_sortenv( env, NULL );
4016 return;
4019 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4020 if (CompareStringW(LOCALE_USER_DEFAULT,
4021 NORM_IGNORECASE | SORT_STRINGSORT,
4022 s, 2, parmP, -1) == CSTR_EQUAL) {
4023 DWORD count;
4025 s += 2;
4026 while (*s && (*s==' ' || *s=='\t')) s++;
4027 if (*s=='\"')
4028 WCMD_strip_quotes(s);
4030 /* If no parameter, or no '=' sign, return an error */
4031 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
4032 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4033 return;
4036 /* Output the prompt */
4037 *p++ = '\0';
4038 if (strlenW(p) != 0) WCMD_output_asis(p);
4040 /* Read the reply */
4041 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4042 if (count > 1) {
4043 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4044 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4045 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4046 wine_dbgstr_w(string));
4047 status = SetEnvironmentVariableW(s, string);
4050 /* See if /A supplied, and if so calculate the results of all the expressions */
4051 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4052 NORM_IGNORECASE | SORT_STRINGSORT,
4053 s, 2, parmA, -1) == CSTR_EQUAL) {
4054 /* /A supplied, so evaluate expressions and set variables appropriately */
4055 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4056 /* of the final computation */
4057 int result = 0;
4058 int rc = 0;
4059 WCHAR *thisexpr;
4060 WCHAR *src,*dst;
4062 /* Remove all quotes before doing any calculations */
4063 thisexpr = heap_alloc((strlenW(s+2)+1) * sizeof(WCHAR));
4064 src = s+2;
4065 dst = thisexpr;
4066 while (*src) {
4067 if (*src != '"') *dst++ = *src;
4068 src++;
4070 *dst = 0;
4072 /* Now calculate the results of the expression */
4073 src = thisexpr;
4074 rc = WCMD_handleExpression(&src, &result, 0);
4075 heap_free(thisexpr);
4077 /* If parsing failed, issue the error message */
4078 if (rc > 0) {
4079 WCMD_output_stderr(WCMD_LoadMessage(rc));
4080 return;
4083 /* If we have no context (interactive or cmd.exe /c) print the final result */
4084 if (!context) {
4085 static const WCHAR fmt[] = {'%','d','\0'};
4086 sprintfW(string, fmt, result);
4087 WCMD_output_asis(string);
4090 } else {
4091 DWORD gle;
4093 if (*s=='\"')
4094 WCMD_strip_quotes(s);
4095 p = strchrW (s, '=');
4096 if (p == NULL) {
4097 env = GetEnvironmentStringsW();
4098 if (WCMD_setshow_sortenv( env, s ) == 0) {
4099 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4100 errorlevel = 1;
4102 return;
4104 *p++ = '\0';
4106 if (strlenW(p) == 0) p = NULL;
4107 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4108 wine_dbgstr_w(p));
4109 status = SetEnvironmentVariableW(s, p);
4110 gle = GetLastError();
4111 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4112 errorlevel = 1;
4113 } else if ((!status)) WCMD_print_error();
4114 else errorlevel = 0;
4118 /****************************************************************************
4119 * WCMD_setshow_path
4121 * Set/Show the path environment variable
4124 void WCMD_setshow_path (const WCHAR *args) {
4126 WCHAR string[1024];
4127 DWORD status;
4128 static const WCHAR pathW[] = {'P','A','T','H','\0'};
4129 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
4131 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
4132 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
4133 if (status != 0) {
4134 WCMD_output_asis ( pathEqW);
4135 WCMD_output_asis ( string);
4136 WCMD_output_asis ( newlineW);
4138 else {
4139 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4142 else {
4143 if (*args == '=') args++; /* Skip leading '=' */
4144 status = SetEnvironmentVariableW(pathW, args);
4145 if (!status) WCMD_print_error();
4149 /****************************************************************************
4150 * WCMD_setshow_prompt
4152 * Set or show the command prompt.
4155 void WCMD_setshow_prompt (void) {
4157 WCHAR *s;
4158 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
4160 if (strlenW(param1) == 0) {
4161 SetEnvironmentVariableW(promptW, NULL);
4163 else {
4164 s = param1;
4165 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4166 if (strlenW(s) == 0) {
4167 SetEnvironmentVariableW(promptW, NULL);
4169 else SetEnvironmentVariableW(promptW, s);
4173 /****************************************************************************
4174 * WCMD_setshow_time
4176 * Set/Show the system time
4177 * FIXME: Can't change time yet
4180 void WCMD_setshow_time (void) {
4182 WCHAR curtime[64], buffer[64];
4183 DWORD count;
4184 SYSTEMTIME st;
4185 static const WCHAR parmT[] = {'/','T','\0'};
4187 if (strlenW(param1) == 0) {
4188 GetLocalTime(&st);
4189 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
4190 curtime, sizeof(curtime)/sizeof(WCHAR))) {
4191 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4192 if (strstrW (quals, parmT) == NULL) {
4193 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4194 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4195 if (count > 2) {
4196 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4200 else WCMD_print_error ();
4202 else {
4203 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4207 /****************************************************************************
4208 * WCMD_shift
4210 * Shift batch parameters.
4211 * Optional /n says where to start shifting (n=0-8)
4214 void WCMD_shift (const WCHAR *args) {
4215 int start;
4217 if (context != NULL) {
4218 WCHAR *pos = strchrW(args, '/');
4219 int i;
4221 if (pos == NULL) {
4222 start = 0;
4223 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4224 start = (*(pos+1) - '0');
4225 } else {
4226 SetLastError(ERROR_INVALID_PARAMETER);
4227 WCMD_print_error();
4228 return;
4231 WINE_TRACE("Shifting variables, starting at %d\n", start);
4232 for (i=start;i<=8;i++) {
4233 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4235 context -> shift_count[9] = context -> shift_count[9] + 1;
4240 /****************************************************************************
4241 * WCMD_start
4243 void WCMD_start(const WCHAR *args)
4245 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
4246 '\\','s','t','a','r','t','.','e','x','e',0};
4247 WCHAR file[MAX_PATH];
4248 WCHAR *cmdline;
4249 STARTUPINFOW st;
4250 PROCESS_INFORMATION pi;
4252 GetWindowsDirectoryW( file, MAX_PATH );
4253 strcatW( file, exeW );
4254 cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
4255 strcpyW( cmdline, file );
4256 strcatW( cmdline, spaceW );
4257 strcatW( cmdline, args );
4259 memset( &st, 0, sizeof(STARTUPINFOW) );
4260 st.cb = sizeof(STARTUPINFOW);
4262 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4264 WaitForSingleObject( pi.hProcess, INFINITE );
4265 GetExitCodeProcess( pi.hProcess, &errorlevel );
4266 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4267 CloseHandle(pi.hProcess);
4268 CloseHandle(pi.hThread);
4270 else
4272 SetLastError(ERROR_FILE_NOT_FOUND);
4273 WCMD_print_error ();
4274 errorlevel = 9009;
4276 heap_free(cmdline);
4279 /****************************************************************************
4280 * WCMD_title
4282 * Set the console title
4284 void WCMD_title (const WCHAR *args) {
4285 SetConsoleTitleW(args);
4288 /****************************************************************************
4289 * WCMD_type
4291 * Copy a file to standard output.
4294 void WCMD_type (WCHAR *args) {
4296 int argno = 0;
4297 WCHAR *argN = args;
4298 BOOL writeHeaders = FALSE;
4300 if (param1[0] == 0x00) {
4301 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4302 return;
4305 if (param2[0] != 0x00) writeHeaders = TRUE;
4307 /* Loop through all args */
4308 errorlevel = 0;
4309 while (argN) {
4310 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4312 HANDLE h;
4313 WCHAR buffer[512];
4314 DWORD count;
4316 if (!argN) break;
4318 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4319 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4320 FILE_ATTRIBUTE_NORMAL, NULL);
4321 if (h == INVALID_HANDLE_VALUE) {
4322 WCMD_print_error ();
4323 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4324 errorlevel = 1;
4325 } else {
4326 if (writeHeaders) {
4327 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
4328 WCMD_output(fmt, thisArg);
4330 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
4331 if (count == 0) break; /* ReadFile reports success on EOF! */
4332 buffer[count] = 0;
4333 WCMD_output_asis (buffer);
4335 CloseHandle (h);
4340 /****************************************************************************
4341 * WCMD_more
4343 * Output either a file or stdin to screen in pages
4346 void WCMD_more (WCHAR *args) {
4348 int argno = 0;
4349 WCHAR *argN = args;
4350 WCHAR moreStr[100];
4351 WCHAR moreStrPage[100];
4352 WCHAR buffer[512];
4353 DWORD count;
4354 static const WCHAR moreStart[] = {'-','-',' ','\0'};
4355 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
4356 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
4357 ')',' ','-','-','\n','\0'};
4358 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
4360 /* Prefix the NLS more with '-- ', then load the text */
4361 errorlevel = 0;
4362 strcpyW(moreStr, moreStart);
4363 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
4364 (sizeof(moreStr)/sizeof(WCHAR))-3);
4366 if (param1[0] == 0x00) {
4368 /* Wine implements pipes via temporary files, and hence stdin is
4369 effectively reading from the file. This means the prompts for
4370 more are satisfied by the next line from the input (file). To
4371 avoid this, ensure stdin is to the console */
4372 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4373 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
4374 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4375 FILE_ATTRIBUTE_NORMAL, 0);
4376 WINE_TRACE("No parms - working probably in pipe mode\n");
4377 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4379 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4380 once you get in this bit unless due to a pipe, its going to end badly... */
4381 wsprintfW(moreStrPage, moreFmt, moreStr);
4383 WCMD_enter_paged_mode(moreStrPage);
4384 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4385 if (count == 0) break; /* ReadFile reports success on EOF! */
4386 buffer[count] = 0;
4387 WCMD_output_asis (buffer);
4389 WCMD_leave_paged_mode();
4391 /* Restore stdin to what it was */
4392 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4393 CloseHandle(hConIn);
4395 return;
4396 } else {
4397 BOOL needsPause = FALSE;
4399 /* Loop through all args */
4400 WINE_TRACE("Parms supplied - working through each file\n");
4401 WCMD_enter_paged_mode(moreStrPage);
4403 while (argN) {
4404 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4405 HANDLE h;
4407 if (!argN) break;
4409 if (needsPause) {
4411 /* Wait */
4412 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
4413 WCMD_leave_paged_mode();
4414 WCMD_output_asis(moreStrPage);
4415 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4416 WCMD_enter_paged_mode(moreStrPage);
4420 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4421 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4422 FILE_ATTRIBUTE_NORMAL, NULL);
4423 if (h == INVALID_HANDLE_VALUE) {
4424 WCMD_print_error ();
4425 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4426 errorlevel = 1;
4427 } else {
4428 ULONG64 curPos = 0;
4429 ULONG64 fileLen = 0;
4430 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4432 /* Get the file size */
4433 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4434 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4436 needsPause = TRUE;
4437 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4438 if (count == 0) break; /* ReadFile reports success on EOF! */
4439 buffer[count] = 0;
4440 curPos += count;
4442 /* Update % count (would be used in WCMD_output_asis as prompt) */
4443 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
4445 WCMD_output_asis (buffer);
4447 CloseHandle (h);
4451 WCMD_leave_paged_mode();
4455 /****************************************************************************
4456 * WCMD_verify
4458 * Display verify flag.
4459 * FIXME: We don't actually do anything with the verify flag other than toggle
4460 * it...
4463 void WCMD_verify (const WCHAR *args) {
4465 int count;
4467 count = strlenW(args);
4468 if (count == 0) {
4469 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
4470 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
4471 return;
4473 if (lstrcmpiW(args, onW) == 0) {
4474 verify_mode = TRUE;
4475 return;
4477 else if (lstrcmpiW(args, offW) == 0) {
4478 verify_mode = FALSE;
4479 return;
4481 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4484 /****************************************************************************
4485 * WCMD_version
4487 * Display version info.
4490 void WCMD_version (void) {
4492 WCMD_output_asis (version_string);
4496 /****************************************************************************
4497 * WCMD_volume
4499 * Display volume information (set_label = FALSE)
4500 * Additionally set volume label (set_label = TRUE)
4501 * Returns 1 on success, 0 otherwise
4504 int WCMD_volume(BOOL set_label, const WCHAR *path)
4506 DWORD count, serial;
4507 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4508 BOOL status;
4510 if (strlenW(path) == 0) {
4511 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
4512 if (!status) {
4513 WCMD_print_error ();
4514 return 0;
4516 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
4517 &serial, NULL, NULL, NULL, 0);
4519 else {
4520 static const WCHAR fmt[] = {'%','s','\\','\0'};
4521 if ((path[1] != ':') || (strlenW(path) != 2)) {
4522 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4523 return 0;
4525 wsprintfW (curdir, fmt, path);
4526 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
4527 &serial, NULL,
4528 NULL, NULL, 0);
4530 if (!status) {
4531 WCMD_print_error ();
4532 return 0;
4534 if (label[0] != '\0') {
4535 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4536 curdir[0], label);
4538 else {
4539 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4540 curdir[0]);
4542 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4543 HIWORD(serial), LOWORD(serial));
4544 if (set_label) {
4545 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4546 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4547 if (count > 1) {
4548 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4549 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4551 if (strlenW(path) != 0) {
4552 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4554 else {
4555 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4558 return 1;
4561 /**************************************************************************
4562 * WCMD_exit
4564 * Exit either the process, or just this batch program
4568 void WCMD_exit (CMD_LIST **cmdList) {
4570 static const WCHAR parmB[] = {'/','B','\0'};
4571 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
4573 if (context && lstrcmpiW(quals, parmB) == 0) {
4574 errorlevel = rc;
4575 context -> skip_rest = TRUE;
4576 *cmdList = NULL;
4577 } else {
4578 ExitProcess(rc);
4583 /*****************************************************************************
4584 * WCMD_assoc
4586 * Lists or sets file associations (assoc = TRUE)
4587 * Lists or sets file types (assoc = FALSE)
4589 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4591 HKEY key;
4592 DWORD accessOptions = KEY_READ;
4593 WCHAR *newValue;
4594 LONG rc = ERROR_SUCCESS;
4595 WCHAR keyValue[MAXSTRING];
4596 DWORD valueLen = MAXSTRING;
4597 HKEY readKey;
4598 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
4599 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4601 /* See if parameter includes '=' */
4602 errorlevel = 0;
4603 newValue = strchrW(args, '=');
4604 if (newValue) accessOptions |= KEY_WRITE;
4606 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4607 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
4608 accessOptions, &key) != ERROR_SUCCESS) {
4609 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4610 return;
4613 /* If no parameters then list all associations */
4614 if (*args == 0x00) {
4615 int index = 0;
4617 /* Enumerate all the keys */
4618 while (rc != ERROR_NO_MORE_ITEMS) {
4619 WCHAR keyName[MAXSTRING];
4620 DWORD nameLen;
4622 /* Find the next value */
4623 nameLen = MAXSTRING;
4624 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4626 if (rc == ERROR_SUCCESS) {
4628 /* Only interested in extension ones if assoc, or others
4629 if not assoc */
4630 if ((keyName[0] == '.' && assoc) ||
4631 (!(keyName[0] == '.') && (!assoc)))
4633 WCHAR subkey[MAXSTRING];
4634 strcpyW(subkey, keyName);
4635 if (!assoc) strcatW(subkey, shOpCmdW);
4637 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4639 valueLen = sizeof(keyValue)/sizeof(WCHAR);
4640 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4641 WCMD_output_asis(keyName);
4642 WCMD_output_asis(equalW);
4643 /* If no default value found, leave line empty after '=' */
4644 if (rc == ERROR_SUCCESS) {
4645 WCMD_output_asis(keyValue);
4647 WCMD_output_asis(newlineW);
4648 RegCloseKey(readKey);
4654 } else {
4656 /* Parameter supplied - if no '=' on command line, its a query */
4657 if (newValue == NULL) {
4658 WCHAR *space;
4659 WCHAR subkey[MAXSTRING];
4661 /* Query terminates the parameter at the first space */
4662 strcpyW(keyValue, args);
4663 space = strchrW(keyValue, ' ');
4664 if (space) *space=0x00;
4666 /* Set up key name */
4667 strcpyW(subkey, keyValue);
4668 if (!assoc) strcatW(subkey, shOpCmdW);
4670 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4672 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4673 WCMD_output_asis(args);
4674 WCMD_output_asis(equalW);
4675 /* If no default value found, leave line empty after '=' */
4676 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4677 WCMD_output_asis(newlineW);
4678 RegCloseKey(readKey);
4680 } else {
4681 WCHAR msgbuffer[MAXSTRING];
4683 /* Load the translated 'File association not found' */
4684 if (assoc) {
4685 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4686 } else {
4687 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4689 WCMD_output_stderr(msgbuffer, keyValue);
4690 errorlevel = 2;
4693 /* Not a query - its a set or clear of a value */
4694 } else {
4696 WCHAR subkey[MAXSTRING];
4698 /* Get pointer to new value */
4699 *newValue = 0x00;
4700 newValue++;
4702 /* Set up key name */
4703 strcpyW(subkey, args);
4704 if (!assoc) strcatW(subkey, shOpCmdW);
4706 /* If nothing after '=' then clear value - only valid for ASSOC */
4707 if (*newValue == 0x00) {
4709 if (assoc) rc = RegDeleteKeyW(key, args);
4710 if (assoc && rc == ERROR_SUCCESS) {
4711 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4713 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4714 WCMD_print_error();
4715 errorlevel = 2;
4717 } else {
4718 WCHAR msgbuffer[MAXSTRING];
4720 /* Load the translated 'File association not found' */
4721 if (assoc) {
4722 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
4723 sizeof(msgbuffer)/sizeof(WCHAR));
4724 } else {
4725 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
4726 sizeof(msgbuffer)/sizeof(WCHAR));
4728 WCMD_output_stderr(msgbuffer, keyValue);
4729 errorlevel = 2;
4732 /* It really is a set value = contents */
4733 } else {
4734 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4735 accessOptions, NULL, &readKey, NULL);
4736 if (rc == ERROR_SUCCESS) {
4737 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4738 (LPBYTE)newValue,
4739 sizeof(WCHAR) * (strlenW(newValue) + 1));
4740 RegCloseKey(readKey);
4743 if (rc != ERROR_SUCCESS) {
4744 WCMD_print_error();
4745 errorlevel = 2;
4746 } else {
4747 WCMD_output_asis(args);
4748 WCMD_output_asis(equalW);
4749 WCMD_output_asis(newValue);
4750 WCMD_output_asis(newlineW);
4756 /* Clean up */
4757 RegCloseKey(key);
4760 /****************************************************************************
4761 * WCMD_color
4763 * Colors the terminal screen.
4766 void WCMD_color (void) {
4768 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4769 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4771 if (param1[0] != 0x00 && strlenW(param1) > 2) {
4772 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4773 return;
4776 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4778 COORD topLeft;
4779 DWORD screenSize;
4780 DWORD color = 0;
4782 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4784 topLeft.X = 0;
4785 topLeft.Y = 0;
4787 /* Convert the color hex digits */
4788 if (param1[0] == 0x00) {
4789 color = defaultColor;
4790 } else {
4791 color = strtoulW(param1, NULL, 16);
4794 /* Fail if fg == bg color */
4795 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4796 errorlevel = 1;
4797 return;
4800 /* Set the current screen contents and ensure all future writes
4801 remain this color */
4802 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4803 SetConsoleTextAttribute(hStdOut, color);