configure: Get rid of no longer needed OpenSSL checks.
[wine.git] / programs / cmd / builtins.c
blob076b8522f12df10a7a81b35d6d61b0dc1d894c61
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(%p), doIt(%d)\n",
1507 cmdList, wine_dbgstr_w(firstcmd),
1508 executecmds);
1510 /* Skip leading whitespace between condition and the command */
1511 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1513 /* Process the first command, if there is one */
1514 if (executecmds && firstcmd && *firstcmd) {
1515 WCHAR *command = heap_strdupW(firstcmd);
1516 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1517 heap_free(command);
1521 /* If it didn't move the position, step to next command */
1522 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1524 /* Process any other parts of the command */
1525 if (*cmdList) {
1526 BOOL processThese = executecmds;
1528 while (*cmdList) {
1529 static const WCHAR ifElse[] = {'e','l','s','e'};
1531 /* execute all appropriate commands */
1532 curPosition = *cmdList;
1534 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1535 *cmdList,
1536 (*cmdList)->prevDelim,
1537 (*cmdList)->bracketDepth, myDepth);
1539 /* Execute any statements appended to the line */
1540 /* FIXME: Only if previous call worked for && or failed for || */
1541 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1542 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1543 if (processThese && (*cmdList)->command) {
1544 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1545 cmdList, FALSE);
1547 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1549 /* Execute any appended to the statement with (...) */
1550 } else if ((*cmdList)->bracketDepth > myDepth) {
1551 if (processThese) {
1552 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1553 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1555 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1557 /* End of the command - does 'ELSE ' follow as the next command? */
1558 } else {
1559 if (isIF
1560 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1561 (*cmdList)->command)) {
1563 /* Swap between if and else processing */
1564 processThese = !processThese;
1566 /* Process the ELSE part */
1567 if (processThese) {
1568 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1569 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1571 /* Skip leading whitespace between condition and the command */
1572 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1573 if (*cmd) {
1574 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1577 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1578 } else {
1579 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1580 break;
1585 return;
1588 /*****************************************************************************
1589 * WCMD_parse_forf_options
1591 * Parses the for /f 'options', extracting the values and validating the
1592 * keywords. Note all keywords are optional.
1593 * Parameters:
1594 * options [I] The unparsed parameter string
1595 * eol [O] Set to the comment character (eol=x)
1596 * skip [O] Set to the number of lines to skip (skip=xx)
1597 * delims [O] Set to the token delimiters (delims=)
1598 * tokens [O] Set to the requested tokens, as provided (tokens=)
1599 * usebackq [O] Set to TRUE if usebackq found
1601 * Returns TRUE on success, FALSE on syntax error
1604 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1605 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1608 WCHAR *pos = options;
1609 int len = strlenW(pos);
1610 static const WCHAR eolW[] = {'e','o','l','='};
1611 static const WCHAR skipW[] = {'s','k','i','p','='};
1612 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1613 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1614 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1615 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1616 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1618 /* Initialize to defaults */
1619 strcpyW(delims, forf_defaultdelims);
1620 strcpyW(tokens, forf_defaulttokens);
1621 *eol = 0;
1622 *skip = 0;
1623 *usebackq = FALSE;
1625 /* Strip (optional) leading and trailing quotes */
1626 if ((*pos == '"') && (pos[len-1] == '"')) {
1627 pos[len-1] = 0;
1628 pos++;
1631 /* Process each keyword */
1632 while (pos && *pos) {
1633 if (*pos == ' ' || *pos == '\t') {
1634 pos++;
1636 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1637 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1638 pos, sizeof(eolW)/sizeof(WCHAR),
1639 eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1640 *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1641 pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1642 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1644 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1645 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1646 pos, sizeof(skipW)/sizeof(WCHAR),
1647 skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1648 WCHAR *nextchar = NULL;
1649 pos = pos + sizeof(skipW)/sizeof(WCHAR);
1650 *skip = strtoulW(pos, &nextchar, 0);
1651 WINE_TRACE("Found skip as %d lines\n", *skip);
1652 pos = nextchar;
1654 /* Save if usebackq semantics are in effect */
1655 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1656 pos, sizeof(usebackqW)/sizeof(WCHAR),
1657 usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1658 *usebackq = TRUE;
1659 pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1660 WINE_TRACE("Found usebackq\n");
1662 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1663 if you finish the optionsroot string with delims= otherwise the space is
1664 just a token delimiter! */
1665 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1666 pos, sizeof(delimsW)/sizeof(WCHAR),
1667 delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1668 int i=0;
1670 pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1671 while (*pos && *pos != ' ') {
1672 delims[i++] = *pos;
1673 pos++;
1675 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1676 delims[i++] = 0; /* Null terminate the delims */
1677 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1679 /* Save the tokens being requested */
1680 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1681 pos, sizeof(tokensW)/sizeof(WCHAR),
1682 tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1683 int i=0;
1685 pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1686 while (*pos && *pos != ' ') {
1687 tokens[i++] = *pos;
1688 pos++;
1690 tokens[i++] = 0; /* Null terminate the tokens */
1691 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1693 } else {
1694 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1695 return FALSE;
1698 return TRUE;
1701 /*****************************************************************************
1702 * WCMD_add_dirstowalk
1704 * When recursing through directories (for /r), we need to add to the list of
1705 * directories still to walk, any subdirectories of the one we are processing.
1707 * Parameters
1708 * options [I] The remaining list of directories still to process
1710 * Note this routine inserts the subdirectories found between the entry being
1711 * processed, and any other directory still to be processed, mimicing what
1712 * Windows does
1714 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1715 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1716 WCHAR fullitem[MAX_PATH];
1717 WIN32_FIND_DATAW fd;
1718 HANDLE hff;
1720 /* Build a generic search and add all directories on the list of directories
1721 still to walk */
1722 strcpyW(fullitem, dirsToWalk->dirName);
1723 strcatW(fullitem, slashstarW);
1724 hff = FindFirstFileW(fullitem, &fd);
1725 if (hff != INVALID_HANDLE_VALUE) {
1726 do {
1727 WINE_TRACE("Looking for subdirectories\n");
1728 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1729 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1730 (strcmpW(fd.cFileName, dotW) != 0))
1732 /* Allocate memory, add to list */
1733 DIRECTORY_STACK *toWalk = heap_alloc(sizeof(DIRECTORY_STACK));
1734 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1735 toWalk->next = remainingDirs->next;
1736 remainingDirs->next = toWalk;
1737 remainingDirs = toWalk;
1738 toWalk->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1739 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1740 strcatW(toWalk->dirName, slashW);
1741 strcatW(toWalk->dirName, fd.cFileName);
1742 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1743 toWalk, toWalk->next);
1745 } while (FindNextFileW(hff, &fd) != 0);
1746 WINE_TRACE("Finished adding all subdirectories\n");
1747 FindClose (hff);
1751 /**************************************************************************
1752 * WCMD_for_nexttoken
1754 * Parse the token= line, identifying the next highest number not processed
1755 * so far. Count how many tokens are referred (including duplicates) and
1756 * optionally return that, plus optionally indicate if the tokens= line
1757 * ends in a star.
1759 * Parameters:
1760 * lasttoken [I] - Identifies the token index of the last one
1761 * returned so far (-1 used for first loop)
1762 * tokenstr [I] - The specified tokens= line
1763 * firstCmd [O] - Optionally indicate how many tokens are listed
1764 * doAll [O] - Optionally indicate if line ends with *
1765 * duplicates [O] - Optionally indicate if there is any evidence of
1766 * overlaying tokens in the string
1767 * Note the caller should keep a running track of duplicates as the tokens
1768 * are recursively passed. If any have duplicates, then the * token should
1769 * not be honoured.
1771 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1772 int *totalfound, BOOL *doall,
1773 BOOL *duplicates)
1775 WCHAR *pos = tokenstr;
1776 int nexttoken = -1;
1778 if (totalfound) *totalfound = 0;
1779 if (doall) *doall = FALSE;
1780 if (duplicates) *duplicates = FALSE;
1782 WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken,
1783 wine_dbgstr_w(tokenstr), nexttoken);
1785 /* Loop through the token string, parsing it. Valid syntax is:
1786 token=m or x-y with comma delimiter and optionally * to finish*/
1787 while (*pos) {
1788 int nextnumber1, nextnumber2 = -1;
1789 WCHAR *nextchar;
1791 /* Get the next number */
1792 nextnumber1 = strtoulW(pos, &nextchar, 10);
1794 /* If it is followed by a minus, its a range, so get the next one as well */
1795 if (*nextchar == '-') {
1796 nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1798 /* We want to return the lowest number that is higher than lasttoken
1799 but only if range is positive */
1800 if (nextnumber2 >= nextnumber1 &&
1801 lasttoken < nextnumber2) {
1803 int nextvalue;
1804 if (nexttoken == -1) {
1805 nextvalue = max(nextnumber1, (lasttoken+1));
1806 } else {
1807 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1810 /* Flag if duplicates identified */
1811 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1813 nexttoken = nextvalue;
1816 /* Update the running total for the whole range */
1817 if (nextnumber2 >= nextnumber1 && totalfound) {
1818 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1821 } else {
1822 if (totalfound) (*totalfound)++;
1824 /* See if the number found is one we have already seen */
1825 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1827 /* We want to return the lowest number that is higher than lasttoken */
1828 if (lasttoken < nextnumber1 &&
1829 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1830 nexttoken = nextnumber1;
1835 /* Remember if it is followed by a star, and if it is indicate a need to
1836 show all tokens, unless a duplicate has been found */
1837 if (*nextchar == '*') {
1838 if (doall) *doall = TRUE;
1839 if (totalfound) (*totalfound)++;
1842 /* Step on to the next character */
1843 pos = nextchar;
1844 if (*pos) pos++;
1847 /* Return result */
1848 if (nexttoken == -1) nexttoken = lasttoken;
1849 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1850 if (totalfound) WINE_TRACE("Found total tokens in total %d\n", *totalfound);
1851 if (doall && *doall) WINE_TRACE("Request for all tokens found\n");
1852 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1853 return nexttoken;
1856 /**************************************************************************
1857 * WCMD_parse_line
1859 * When parsing file or string contents (for /f), once the string to parse
1860 * has been identified, handle the various options and call the do part
1861 * if appropriate.
1863 * Parameters:
1864 * cmdStart [I] - Identifies the list of commands making up the
1865 * for loop body (especially if brackets in use)
1866 * firstCmd [I] - The textual start of the command after the DO
1867 * which is within the first item of cmdStart
1868 * cmdEnd [O] - Identifies where to continue after the DO
1869 * variable [I] - The variable identified on the for line
1870 * buffer [I] - The string to parse
1871 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1872 * forf_skip [I/O] - How many lines to skip first
1873 * forf_eol [I] - The 'end of line' (comment) character
1874 * forf_delims [I] - The delimiters to use when breaking the string apart
1875 * forf_tokens [I] - The tokens to use when breaking the string apart
1877 static void WCMD_parse_line(CMD_LIST *cmdStart,
1878 const WCHAR *firstCmd,
1879 CMD_LIST **cmdEnd,
1880 const WCHAR variable,
1881 WCHAR *buffer,
1882 BOOL *doExecuted,
1883 int *forf_skip,
1884 WCHAR forf_eol,
1885 WCHAR *forf_delims,
1886 WCHAR *forf_tokens) {
1888 WCHAR *parm;
1889 FOR_CONTEXT oldcontext;
1890 int varidx, varoffset;
1891 int nexttoken, lasttoken = -1;
1892 BOOL starfound = FALSE;
1893 BOOL thisduplicate = FALSE;
1894 BOOL anyduplicates = FALSE;
1895 int totalfound;
1897 /* Skip lines if requested */
1898 if (*forf_skip) {
1899 (*forf_skip)--;
1900 return;
1903 /* Save away any existing for variable context (e.g. nested for loops) */
1904 oldcontext = forloopcontext;
1906 /* Extract the parameters based on the tokens= value (There will always
1907 be some value, as if it is not supplied, it defaults to tokens=1).
1908 Rough logic:
1909 Count how many tokens are named in the line, identify the lowest
1910 Empty (set to null terminated string) that number of named variables
1911 While lasttoken != nextlowest
1912 %letter = parameter number 'nextlowest'
1913 letter++ (if >26 or >52 abort)
1914 Go through token= string finding next lowest number
1915 If token ends in * set %letter = raw position of token(nextnumber+1)
1917 lasttoken = -1;
1918 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1919 NULL, &thisduplicate);
1920 varidx = FOR_VAR_IDX(variable);
1922 /* Empty out variables */
1923 for (varoffset=0;
1924 varidx >= 0 && varoffset<totalfound && ((varidx+varoffset)%26);
1925 varoffset++) {
1926 forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
1927 /* Stop if we walk beyond z or Z */
1928 if (((varidx+varoffset) % 26) == 0) break;
1931 /* Loop extracting the tokens */
1932 varoffset = 0;
1933 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1934 while (varidx >= 0 && (nexttoken > lasttoken)) {
1935 anyduplicates |= thisduplicate;
1937 /* Extract the token number requested and set into the next variable context */
1938 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, FALSE, FALSE, forf_delims);
1939 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1940 varidx + varoffset, wine_dbgstr_w(parm));
1941 if (varidx >=0) {
1942 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1943 varoffset++;
1944 if (((varidx + varoffset) %26) == 0) break;
1947 /* Find the next token */
1948 lasttoken = nexttoken;
1949 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1950 &starfound, &thisduplicate);
1953 /* If all the rest of the tokens were requested, and there is still space in
1954 the variable range, write them now */
1955 if (!anyduplicates && starfound && varidx >= 0 && ((varidx+varoffset) % 26)) {
1956 nexttoken++;
1957 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
1958 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
1959 varidx + varoffset, wine_dbgstr_w(parm));
1960 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1963 /* Execute the body of the foor loop with these values */
1964 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
1965 CMD_LIST *thisCmdStart = cmdStart;
1966 *doExecuted = TRUE;
1967 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
1968 *cmdEnd = thisCmdStart;
1971 /* Free the duplicated strings, and restore the context */
1972 if (varidx >=0) {
1973 int i;
1974 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
1975 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
1976 (forloopcontext.variable[i] != nullW)) {
1977 heap_free(forloopcontext.variable[i]);
1982 /* Restore the original for variable contextx */
1983 forloopcontext = oldcontext;
1986 /**************************************************************************
1987 * WCMD_forf_getinputhandle
1989 * Return a file handle which can be used for reading the input lines,
1990 * either to a specific file (which may be quote delimited as we have to
1991 * read the parameters in raw mode) or to a command which we need to
1992 * execute. The command being executed runs in its own shell and stores
1993 * its data in a temporary file.
1995 * Parameters:
1996 * usebackq [I] - Indicates whether usebackq is in effect or not
1997 * itemStr [I] - The item to be handled, either a filename or
1998 * whole command string to execute
1999 * iscmd [I] - Identifies whether this is a command or not
2001 * Returns a file handle which can be used to read the input lines from.
2003 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2004 WCHAR temp_str[MAX_PATH];
2005 WCHAR temp_file[MAX_PATH];
2006 WCHAR temp_cmd[MAXSTRING];
2007 HANDLE hinput = INVALID_HANDLE_VALUE;
2008 static const WCHAR redirOutW[] = {'>','%','s','\0'};
2009 static const WCHAR cmdW[] = {'C','M','D','\0'};
2010 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
2011 '/','C',' ','"','%','s','"','\0'};
2013 /* Remove leading and trailing character */
2014 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2015 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2016 (!iscmd && (itemstr[0] == '"' && usebackq)))
2018 itemstr[strlenW(itemstr)-1] = 0x00;
2019 itemstr++;
2022 if (iscmd) {
2023 /* Get temp filename */
2024 GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
2025 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
2027 /* Redirect output to the temporary file */
2028 wsprintfW(temp_str, redirOutW, temp_file);
2029 wsprintfW(temp_cmd, cmdslashcW, itemstr);
2030 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2031 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2032 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2034 /* Open the file, read line by line and process */
2035 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2036 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2038 } else {
2039 /* Open the file, read line by line and process */
2040 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2041 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2042 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2044 return hinput;
2047 /**************************************************************************
2048 * WCMD_for
2050 * Batch file loop processing.
2052 * On entry: cmdList contains the syntax up to the set
2053 * next cmdList and all in that bracket contain the set data
2054 * next cmdlist contains the DO cmd
2055 * following that is either brackets or && entries (as per if)
2059 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2061 WIN32_FIND_DATAW fd;
2062 HANDLE hff;
2063 int i;
2064 static const WCHAR inW[] = {'i','n'};
2065 static const WCHAR doW[] = {'d','o'};
2066 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2067 WCHAR variable[4];
2068 int varidx = -1;
2069 WCHAR *oldvariablevalue;
2070 WCHAR *firstCmd;
2071 int thisDepth;
2072 WCHAR optionsRoot[MAX_PATH];
2073 DIRECTORY_STACK *dirsToWalk = NULL;
2074 BOOL expandDirs = FALSE;
2075 BOOL useNumbers = FALSE;
2076 BOOL doFileset = FALSE;
2077 BOOL doRecurse = FALSE;
2078 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2079 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2080 int itemNum;
2081 CMD_LIST *thisCmdStart;
2082 int parameterNo = 0;
2083 WCHAR forf_eol = 0;
2084 int forf_skip = 0;
2085 WCHAR forf_delims[256];
2086 WCHAR forf_tokens[MAXSTRING];
2087 BOOL forf_usebackq = FALSE;
2089 /* Handle optional qualifiers (multiple are allowed) */
2090 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2092 optionsRoot[0] = 0;
2093 while (thisArg && *thisArg == '/') {
2094 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2095 thisArg++;
2096 switch (toupperW(*thisArg)) {
2097 case 'D': expandDirs = TRUE; break;
2098 case 'L': useNumbers = TRUE; break;
2100 /* Recursive is special case - /R can have an optional path following it */
2101 /* filenamesets are another special case - /F can have an optional options following it */
2102 case 'R':
2103 case 'F':
2105 /* When recursing directories, use current directory as the starting point unless
2106 subsequently overridden */
2107 doRecurse = (toupperW(*thisArg) == 'R');
2108 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
2110 doFileset = (toupperW(*thisArg) == 'F');
2112 /* Retrieve next parameter to see if is root/options (raw form required
2113 with for /f, or unquoted in for /r) */
2114 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2116 /* Next parm is either qualifier, path/options or variable -
2117 only care about it if it is the path/options */
2118 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2119 parameterNo++;
2120 strcpyW(optionsRoot, thisArg);
2122 break;
2124 default:
2125 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2128 /* Step to next token */
2129 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2132 /* Ensure line continues with variable */
2133 if (!*thisArg || *thisArg != '%') {
2134 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2135 return;
2138 /* With for /f parse the options if provided */
2139 if (doFileset) {
2140 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2141 forf_delims, forf_tokens, &forf_usebackq))
2143 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2144 return;
2147 /* Set up the list of directories to recurse if we are going to */
2148 } else if (doRecurse) {
2149 /* Allocate memory, add to list */
2150 dirsToWalk = heap_alloc(sizeof(DIRECTORY_STACK));
2151 dirsToWalk->next = NULL;
2152 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2153 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2156 /* Variable should follow */
2157 strcpyW(variable, thisArg);
2158 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2159 varidx = FOR_VAR_IDX(variable[1]);
2161 /* Ensure line continues with IN */
2162 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2163 if (!thisArg
2164 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2165 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
2166 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
2167 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2168 return;
2171 /* Save away where the set of data starts and the variable */
2172 thisDepth = (*cmdList)->bracketDepth;
2173 *cmdList = (*cmdList)->nextcommand;
2174 setStart = (*cmdList);
2176 /* Skip until the close bracket */
2177 WINE_TRACE("Searching %p as the set\n", *cmdList);
2178 while (*cmdList &&
2179 (*cmdList)->command != NULL &&
2180 (*cmdList)->bracketDepth > thisDepth) {
2181 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2182 *cmdList = (*cmdList)->nextcommand;
2185 /* Skip the close bracket, if there is one */
2186 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2188 /* Syntax error if missing close bracket, or nothing following it
2189 and once we have the complete set, we expect a DO */
2190 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2191 if ((*cmdList == NULL)
2192 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
2194 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2195 return;
2198 cmdEnd = *cmdList;
2200 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2201 mode, or once for the rest of the time. */
2202 do {
2204 /* Save away the starting position for the commands (and offset for the
2205 first one) */
2206 cmdStart = *cmdList;
2207 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2208 itemNum = 0;
2210 /* If we are recursing directories (ie /R), add all sub directories now, then
2211 prefix the root when searching for the item */
2212 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2214 thisSet = setStart;
2215 /* Loop through all set entries */
2216 while (thisSet &&
2217 thisSet->command != NULL &&
2218 thisSet->bracketDepth >= thisDepth) {
2220 /* Loop through all entries on the same line */
2221 WCHAR *item;
2222 WCHAR *itemStart;
2223 WCHAR buffer[MAXSTRING];
2225 WINE_TRACE("Processing for set %p\n", thisSet);
2226 i = 0;
2227 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2230 * If the parameter within the set has a wildcard then search for matching files
2231 * otherwise do a literal substitution.
2233 static const WCHAR wildcards[] = {'*','?','\0'};
2234 thisCmdStart = cmdStart;
2236 itemNum++;
2237 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2239 if (!useNumbers && !doFileset) {
2240 WCHAR fullitem[MAX_PATH];
2242 /* Now build the item to use / search for in the specified directory,
2243 as it is fully qualified in the /R case */
2244 if (dirsToWalk) {
2245 strcpyW(fullitem, dirsToWalk->dirName);
2246 strcatW(fullitem, slashW);
2247 strcatW(fullitem, item);
2248 } else {
2249 strcpyW(fullitem, item);
2252 if (strpbrkW (fullitem, wildcards)) {
2254 hff = FindFirstFileW(fullitem, &fd);
2255 if (hff != INVALID_HANDLE_VALUE) {
2256 do {
2257 BOOL isDirectory = FALSE;
2259 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2261 /* Handle as files or dirs appropriately, but ignore . and .. */
2262 if (isDirectory == expandDirs &&
2263 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2264 (strcmpW(fd.cFileName, dotW) != 0))
2266 thisCmdStart = cmdStart;
2267 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2269 if (doRecurse) {
2270 strcpyW(fullitem, dirsToWalk->dirName);
2271 strcatW(fullitem, slashW);
2272 strcatW(fullitem, fd.cFileName);
2273 } else {
2274 strcpyW(fullitem, fd.cFileName);
2276 doExecuted = TRUE;
2278 /* Save away any existing for variable context (e.g. nested for loops)
2279 and restore it after executing the body of this for loop */
2280 if (varidx >= 0) {
2281 oldvariablevalue = forloopcontext.variable[varidx];
2282 forloopcontext.variable[varidx] = fullitem;
2284 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2285 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2287 cmdEnd = thisCmdStart;
2289 } while (FindNextFileW(hff, &fd) != 0);
2290 FindClose (hff);
2292 } else {
2293 doExecuted = TRUE;
2295 /* Save away any existing for variable context (e.g. nested for loops)
2296 and restore it after executing the body of this for loop */
2297 if (varidx >= 0) {
2298 oldvariablevalue = forloopcontext.variable[varidx];
2299 forloopcontext.variable[varidx] = fullitem;
2301 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2302 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2304 cmdEnd = thisCmdStart;
2307 } else if (useNumbers) {
2308 /* Convert the first 3 numbers to signed longs and save */
2309 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2310 /* else ignore them! */
2312 /* Filesets - either a list of files, or a command to run and parse the output */
2313 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2314 (forf_usebackq && *itemStart != '\''))) {
2316 HANDLE input;
2317 WCHAR *itemparm;
2319 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2320 wine_dbgstr_w(item));
2322 /* If backquote or single quote, we need to launch that command
2323 and parse the results - use a temporary file */
2324 if ((forf_usebackq && *itemStart == '`') ||
2325 (!forf_usebackq && *itemStart == '\'')) {
2327 /* Use itemstart because the command is the whole set, not just the first token */
2328 itemparm = itemStart;
2329 } else {
2331 /* Use item because the file to process is just the first item in the set */
2332 itemparm = item;
2334 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2336 /* Process the input file */
2337 if (input == INVALID_HANDLE_VALUE) {
2338 WCMD_print_error ();
2339 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2340 errorlevel = 1;
2341 return; /* FOR loop aborts at first failure here */
2343 } else {
2345 /* Read line by line until end of file */
2346 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2347 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2348 &forf_skip, forf_eol, forf_delims, forf_tokens);
2349 buffer[0] = 0;
2351 CloseHandle (input);
2354 /* When we have processed the item as a whole command, abort future set processing */
2355 if (itemparm==itemStart) {
2356 thisSet = NULL;
2357 break;
2360 /* Filesets - A string literal */
2361 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2362 (forf_usebackq && *itemStart == '\''))) {
2364 /* Remove leading and trailing character, ready to parse with delims= delimiters
2365 Note that the last quote is removed from the set and the string terminates
2366 there to mimic windows */
2367 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2368 if (strend) {
2369 *strend = 0x00;
2370 itemStart++;
2373 /* Copy the item away from the global buffer used by WCMD_parameter */
2374 strcpyW(buffer, itemStart);
2375 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2376 &forf_skip, forf_eol, forf_delims, forf_tokens);
2378 /* Only one string can be supplied in the whole set, abort future set processing */
2379 thisSet = NULL;
2380 break;
2383 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2384 i++;
2387 /* Move onto the next set line */
2388 if (thisSet) thisSet = thisSet->nextcommand;
2391 /* If /L is provided, now run the for loop */
2392 if (useNumbers) {
2393 WCHAR thisNum[20];
2394 static const WCHAR fmt[] = {'%','d','\0'};
2396 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2397 numbers[0], numbers[2], numbers[1]);
2398 for (i=numbers[0];
2399 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2400 i=i + numbers[1]) {
2402 sprintfW(thisNum, fmt, i);
2403 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2405 thisCmdStart = cmdStart;
2406 doExecuted = TRUE;
2408 /* Save away any existing for variable context (e.g. nested for loops)
2409 and restore it after executing the body of this for loop */
2410 if (varidx >= 0) {
2411 oldvariablevalue = forloopcontext.variable[varidx];
2412 forloopcontext.variable[varidx] = thisNum;
2414 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2415 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2417 cmdEnd = thisCmdStart;
2420 /* If we are walking directories, move on to any which remain */
2421 if (dirsToWalk != NULL) {
2422 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2423 heap_free(dirsToWalk->dirName);
2424 heap_free(dirsToWalk);
2425 dirsToWalk = nextDir;
2426 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2427 wine_dbgstr_w(dirsToWalk->dirName));
2428 else WINE_TRACE("Finished all directories.\n");
2431 } while (dirsToWalk != NULL);
2433 /* Now skip over the do part if we did not perform the for loop so far.
2434 We store in cmdEnd the next command after the do block, but we only
2435 know this if something was run. If it has not been, we need to calculate
2436 it. */
2437 if (!doExecuted) {
2438 thisCmdStart = cmdStart;
2439 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2440 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2441 cmdEnd = thisCmdStart;
2444 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2445 all processing, OR it should be pointing to the end of && processing OR
2446 it should be pointing at the NULL end of bracket for the DO. The return
2447 value needs to be the NEXT command to execute, which it either is, or
2448 we need to step over the closing bracket */
2449 *cmdList = cmdEnd;
2450 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2453 /**************************************************************************
2454 * WCMD_give_help
2456 * Simple on-line help. Help text is stored in the resource file.
2459 void WCMD_give_help (const WCHAR *args)
2461 size_t i;
2463 args = WCMD_skip_leading_spaces((WCHAR*) args);
2464 if (strlenW(args) == 0) {
2465 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2467 else {
2468 /* Display help message for builtin commands */
2469 for (i=0; i<=WCMD_EXIT; i++) {
2470 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2471 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2472 WCMD_output_asis (WCMD_LoadMessage(i));
2473 return;
2476 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2477 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2478 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2479 args, -1, externals[i], -1) == CSTR_EQUAL) {
2480 WCHAR cmd[128];
2481 static const WCHAR helpW[] = {' ', '/','?','\0'};
2482 strcpyW(cmd, args);
2483 strcatW(cmd, helpW);
2484 WCMD_run_program(cmd, FALSE);
2485 return;
2488 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2490 return;
2493 /****************************************************************************
2494 * WCMD_go_to
2496 * Batch file jump instruction. Not the most efficient algorithm ;-)
2497 * Prints error message if the specified label cannot be found - the file pointer is
2498 * then at EOF, effectively stopping the batch file.
2499 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2502 void WCMD_goto (CMD_LIST **cmdList) {
2504 WCHAR string[MAX_PATH];
2505 WCHAR current[MAX_PATH];
2507 /* Do not process any more parts of a processed multipart or multilines command */
2508 if (cmdList) *cmdList = NULL;
2510 if (context != NULL) {
2511 WCHAR *paramStart = param1, *str;
2512 static const WCHAR eofW[] = {':','e','o','f','\0'};
2514 if (param1[0] == 0x00) {
2515 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2516 return;
2519 /* Handle special :EOF label */
2520 if (lstrcmpiW (eofW, param1) == 0) {
2521 context -> skip_rest = TRUE;
2522 return;
2525 /* Support goto :label as well as goto label */
2526 if (*paramStart == ':') paramStart++;
2528 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2529 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2530 str = string;
2531 while (isspaceW (*str)) str++;
2532 if (*str == ':') {
2533 DWORD index = 0;
2534 str++;
2535 while (((current[index] = str[index])) && (!isspaceW (current[index])))
2536 index++;
2538 /* ignore space at the end */
2539 current[index] = 0;
2540 if (lstrcmpiW (current, paramStart) == 0) return;
2543 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2545 return;
2548 /*****************************************************************************
2549 * WCMD_pushd
2551 * Push a directory onto the stack
2554 void WCMD_pushd (const WCHAR *args)
2556 struct env_stack *curdir;
2557 WCHAR *thisdir;
2558 static const WCHAR parmD[] = {'/','D','\0'};
2560 if (strchrW(args, '/') != NULL) {
2561 SetLastError(ERROR_INVALID_PARAMETER);
2562 WCMD_print_error();
2563 return;
2566 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2567 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2568 if( !curdir || !thisdir ) {
2569 LocalFree(curdir);
2570 LocalFree(thisdir);
2571 WINE_ERR ("out of memory\n");
2572 return;
2575 /* Change directory using CD code with /D parameter */
2576 strcpyW(quals, parmD);
2577 GetCurrentDirectoryW (1024, thisdir);
2578 errorlevel = 0;
2579 WCMD_setshow_default(args);
2580 if (errorlevel) {
2581 LocalFree(curdir);
2582 LocalFree(thisdir);
2583 return;
2584 } else {
2585 curdir -> next = pushd_directories;
2586 curdir -> strings = thisdir;
2587 if (pushd_directories == NULL) {
2588 curdir -> u.stackdepth = 1;
2589 } else {
2590 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2592 pushd_directories = curdir;
2597 /*****************************************************************************
2598 * WCMD_popd
2600 * Pop a directory from the stack
2603 void WCMD_popd (void) {
2604 struct env_stack *temp = pushd_directories;
2606 if (!pushd_directories)
2607 return;
2609 /* pop the old environment from the stack, and make it the current dir */
2610 pushd_directories = temp->next;
2611 SetCurrentDirectoryW(temp->strings);
2612 LocalFree (temp->strings);
2613 LocalFree (temp);
2616 /*******************************************************************
2617 * evaluate_if_comparison
2619 * Evaluates an "if" comparison operation
2621 * PARAMS
2622 * leftOperand [I] left operand, non NULL
2623 * operator [I] "if" binary comparison operator, non NULL
2624 * rightOperand [I] right operand, non NULL
2625 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2627 * RETURNS
2628 * Success: 1 if operator applied to the operands evaluates to TRUE
2629 * 0 if operator applied to the operands evaluates to FALSE
2630 * Failure: -1 if operator is not recognized
2632 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2633 const WCHAR *rightOperand, int caseInsensitive)
2635 WCHAR *endptr_leftOp, *endptr_rightOp;
2636 long int leftOperand_int, rightOperand_int;
2637 BOOL int_operands;
2638 static const WCHAR lssW[] = {'l','s','s','\0'};
2639 static const WCHAR leqW[] = {'l','e','q','\0'};
2640 static const WCHAR equW[] = {'e','q','u','\0'};
2641 static const WCHAR neqW[] = {'n','e','q','\0'};
2642 static const WCHAR geqW[] = {'g','e','q','\0'};
2643 static const WCHAR gtrW[] = {'g','t','r','\0'};
2645 /* == is a special case, as it always compares strings */
2646 if (!lstrcmpiW(operator, eqeqW))
2647 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2648 : lstrcmpW (leftOperand, rightOperand) == 0;
2650 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2651 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2652 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2653 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2655 /* Perform actual (integer or string) comparison */
2656 if (!lstrcmpiW(operator, lssW)) {
2657 if (int_operands)
2658 return leftOperand_int < rightOperand_int;
2659 else
2660 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2661 : lstrcmpW (leftOperand, rightOperand) < 0;
2664 if (!lstrcmpiW(operator, leqW)) {
2665 if (int_operands)
2666 return leftOperand_int <= rightOperand_int;
2667 else
2668 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2669 : lstrcmpW (leftOperand, rightOperand) <= 0;
2672 if (!lstrcmpiW(operator, equW)) {
2673 if (int_operands)
2674 return leftOperand_int == rightOperand_int;
2675 else
2676 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2677 : lstrcmpW (leftOperand, rightOperand) == 0;
2680 if (!lstrcmpiW(operator, neqW)) {
2681 if (int_operands)
2682 return leftOperand_int != rightOperand_int;
2683 else
2684 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2685 : lstrcmpW (leftOperand, rightOperand) != 0;
2688 if (!lstrcmpiW(operator, geqW)) {
2689 if (int_operands)
2690 return leftOperand_int >= rightOperand_int;
2691 else
2692 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2693 : lstrcmpW (leftOperand, rightOperand) >= 0;
2696 if (!lstrcmpiW(operator, gtrW)) {
2697 if (int_operands)
2698 return leftOperand_int > rightOperand_int;
2699 else
2700 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2701 : lstrcmpW (leftOperand, rightOperand) > 0;
2704 return -1;
2707 /****************************************************************************
2708 * WCMD_if
2710 * Batch file conditional.
2712 * On entry, cmdlist will point to command containing the IF, and optionally
2713 * the first command to execute (if brackets not found)
2714 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2715 * If ('s were found, execute all within that bracket
2716 * Command may optionally be followed by an ELSE - need to skip instructions
2717 * in the else using the same logic
2719 * FIXME: Much more syntax checking needed!
2721 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2723 int negate; /* Negate condition */
2724 int test; /* Condition evaluation result */
2725 WCHAR condition[MAX_PATH], *command;
2726 static const WCHAR notW[] = {'n','o','t','\0'};
2727 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2728 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2729 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2730 static const WCHAR parmI[] = {'/','I','\0'};
2731 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2733 negate = !lstrcmpiW(param1,notW);
2734 strcpyW(condition, (negate ? param2 : param1));
2735 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2737 if (!lstrcmpiW (condition, errlvlW)) {
2738 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2739 WCHAR *endptr;
2740 long int param_int = strtolW(param, &endptr, 10);
2741 if (*endptr) goto syntax_err;
2742 test = ((long int)errorlevel >= param_int);
2743 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2745 else if (!lstrcmpiW (condition, existW)) {
2746 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2747 != INVALID_FILE_ATTRIBUTES);
2748 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2750 else if (!lstrcmpiW (condition, defdW)) {
2751 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2752 NULL, 0) > 0);
2753 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2755 else { /* comparison operation */
2756 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2757 WCHAR *paramStart;
2759 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, &paramStart, TRUE, FALSE));
2760 if (!*leftOperand)
2761 goto syntax_err;
2763 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2764 p = paramStart + strlenW(leftOperand);
2765 while (*p == ' ' || *p == '\t')
2766 p++;
2768 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2769 strcpyW(operator, eqeqW);
2770 else {
2771 strcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2772 if (!*operator) goto syntax_err;
2774 p += strlenW(operator);
2776 strcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2777 if (!*rightOperand)
2778 goto syntax_err;
2780 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2781 if (test == -1)
2782 goto syntax_err;
2784 p = paramStart + strlenW(rightOperand);
2785 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2788 /* Process rest of IF statement which is on the same line
2789 Note: This may process all or some of the cmdList (eg a GOTO) */
2790 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2791 return;
2793 syntax_err:
2794 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2797 /****************************************************************************
2798 * WCMD_move
2800 * Move a file, directory tree or wildcarded set of files.
2803 void WCMD_move (void)
2805 int status;
2806 WIN32_FIND_DATAW fd;
2807 HANDLE hff;
2808 WCHAR input[MAX_PATH];
2809 WCHAR output[MAX_PATH];
2810 WCHAR drive[10];
2811 WCHAR dir[MAX_PATH];
2812 WCHAR fname[MAX_PATH];
2813 WCHAR ext[MAX_PATH];
2815 if (param1[0] == 0x00) {
2816 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2817 return;
2820 /* If no destination supplied, assume current directory */
2821 if (param2[0] == 0x00) {
2822 strcpyW(param2, dotW);
2825 /* If 2nd parm is directory, then use original filename */
2826 /* Convert partial path to full path */
2827 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2828 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2829 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2830 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2832 /* Split into components */
2833 WCMD_splitpath(input, drive, dir, fname, ext);
2835 hff = FindFirstFileW(input, &fd);
2836 if (hff == INVALID_HANDLE_VALUE)
2837 return;
2839 do {
2840 WCHAR dest[MAX_PATH];
2841 WCHAR src[MAX_PATH];
2842 DWORD attribs;
2843 BOOL ok = TRUE;
2845 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2847 /* Build src & dest name */
2848 strcpyW(src, drive);
2849 strcatW(src, dir);
2851 /* See if dest is an existing directory */
2852 attribs = GetFileAttributesW(output);
2853 if (attribs != INVALID_FILE_ATTRIBUTES &&
2854 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2855 strcpyW(dest, output);
2856 strcatW(dest, slashW);
2857 strcatW(dest, fd.cFileName);
2858 } else {
2859 strcpyW(dest, output);
2862 strcatW(src, fd.cFileName);
2864 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2865 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2867 /* If destination exists, prompt unless /Y supplied */
2868 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2869 BOOL force = FALSE;
2870 WCHAR copycmd[MAXSTRING];
2871 DWORD len;
2873 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2874 if (strstrW (quals, parmNoY))
2875 force = FALSE;
2876 else if (strstrW (quals, parmY))
2877 force = TRUE;
2878 else {
2879 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2880 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2881 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2882 && ! lstrcmpiW (copycmd, parmY));
2885 /* Prompt if overwriting */
2886 if (!force) {
2887 WCHAR* question;
2889 /* Ask for confirmation */
2890 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2891 ok = WCMD_ask_confirm(question, FALSE, NULL);
2892 LocalFree(question);
2894 /* So delete the destination prior to the move */
2895 if (ok) {
2896 if (!DeleteFileW(dest)) {
2897 WCMD_print_error ();
2898 errorlevel = 1;
2899 ok = FALSE;
2905 if (ok) {
2906 status = MoveFileW(src, dest);
2907 } else {
2908 status = 1; /* Anything other than 0 to prevent error msg below */
2911 if (!status) {
2912 WCMD_print_error ();
2913 errorlevel = 1;
2915 } while (FindNextFileW(hff, &fd) != 0);
2917 FindClose(hff);
2920 /****************************************************************************
2921 * WCMD_pause
2923 * Suspend execution of a batch script until a key is typed
2926 void WCMD_pause (void)
2928 DWORD oldmode;
2929 BOOL have_console;
2930 DWORD count;
2931 WCHAR key;
2932 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2934 have_console = GetConsoleMode(hIn, &oldmode);
2935 if (have_console)
2936 SetConsoleMode(hIn, 0);
2938 WCMD_output_asis(anykey);
2939 WCMD_ReadFile(hIn, &key, 1, &count);
2940 if (have_console)
2941 SetConsoleMode(hIn, oldmode);
2944 /****************************************************************************
2945 * WCMD_remove_dir
2947 * Delete a directory.
2950 void WCMD_remove_dir (WCHAR *args) {
2952 int argno = 0;
2953 int argsProcessed = 0;
2954 WCHAR *argN = args;
2955 static const WCHAR parmS[] = {'/','S','\0'};
2956 static const WCHAR parmQ[] = {'/','Q','\0'};
2958 /* Loop through all args */
2959 while (argN) {
2960 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2961 if (argN && argN[0] != '/') {
2962 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2963 wine_dbgstr_w(quals));
2964 argsProcessed++;
2966 /* If subdirectory search not supplied, just try to remove
2967 and report error if it fails (eg if it contains a file) */
2968 if (strstrW (quals, parmS) == NULL) {
2969 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2971 /* Otherwise use ShFileOp to recursively remove a directory */
2972 } else {
2974 SHFILEOPSTRUCTW lpDir;
2976 /* Ask first */
2977 if (strstrW (quals, parmQ) == NULL) {
2978 BOOL ok;
2979 WCHAR question[MAXSTRING];
2980 static const WCHAR fmt[] = {'%','s',' ','\0'};
2982 /* Ask for confirmation */
2983 wsprintfW(question, fmt, thisArg);
2984 ok = WCMD_ask_confirm(question, TRUE, NULL);
2986 /* Abort if answer is 'N' */
2987 if (!ok) return;
2990 /* Do the delete */
2991 lpDir.hwnd = NULL;
2992 lpDir.pTo = NULL;
2993 lpDir.pFrom = thisArg;
2994 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2995 lpDir.wFunc = FO_DELETE;
2997 /* SHFileOperationW needs file list with a double null termination */
2998 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3000 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3005 /* Handle no valid args */
3006 if (argsProcessed == 0) {
3007 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3008 return;
3013 /****************************************************************************
3014 * WCMD_rename
3016 * Rename a file.
3019 void WCMD_rename (void)
3021 int status;
3022 HANDLE hff;
3023 WIN32_FIND_DATAW fd;
3024 WCHAR input[MAX_PATH];
3025 WCHAR *dotDst = NULL;
3026 WCHAR drive[10];
3027 WCHAR dir[MAX_PATH];
3028 WCHAR fname[MAX_PATH];
3029 WCHAR ext[MAX_PATH];
3031 errorlevel = 0;
3033 /* Must be at least two args */
3034 if (param1[0] == 0x00 || param2[0] == 0x00) {
3035 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3036 errorlevel = 1;
3037 return;
3040 /* Destination cannot contain a drive letter or directory separator */
3041 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
3042 SetLastError(ERROR_INVALID_PARAMETER);
3043 WCMD_print_error();
3044 errorlevel = 1;
3045 return;
3048 /* Convert partial path to full path */
3049 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
3050 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3051 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3052 dotDst = strchrW(param2, '.');
3054 /* Split into components */
3055 WCMD_splitpath(input, drive, dir, fname, ext);
3057 hff = FindFirstFileW(input, &fd);
3058 if (hff == INVALID_HANDLE_VALUE)
3059 return;
3061 do {
3062 WCHAR dest[MAX_PATH];
3063 WCHAR src[MAX_PATH];
3064 WCHAR *dotSrc = NULL;
3065 int dirLen;
3067 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3069 /* FIXME: If dest name or extension is *, replace with filename/ext
3070 part otherwise use supplied name. This supports:
3071 ren *.fred *.jim
3072 ren jim.* fred.* etc
3073 However, windows has a more complex algorithm supporting eg
3074 ?'s and *'s mid name */
3075 dotSrc = strchrW(fd.cFileName, '.');
3077 /* Build src & dest name */
3078 strcpyW(src, drive);
3079 strcatW(src, dir);
3080 strcpyW(dest, src);
3081 dirLen = strlenW(src);
3082 strcatW(src, fd.cFileName);
3084 /* Build name */
3085 if (param2[0] == '*') {
3086 strcatW(dest, fd.cFileName);
3087 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3088 } else {
3089 strcatW(dest, param2);
3090 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3093 /* Build Extension */
3094 if (dotDst && (*(dotDst+1)=='*')) {
3095 if (dotSrc) strcatW(dest, dotSrc);
3096 } else if (dotDst) {
3097 if (dotDst) strcatW(dest, dotDst);
3100 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3101 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3103 status = MoveFileW(src, dest);
3105 if (!status) {
3106 WCMD_print_error ();
3107 errorlevel = 1;
3109 } while (FindNextFileW(hff, &fd) != 0);
3111 FindClose(hff);
3114 /*****************************************************************************
3115 * WCMD_dupenv
3117 * Make a copy of the environment.
3119 static WCHAR *WCMD_dupenv( const WCHAR *env )
3121 WCHAR *env_copy;
3122 int len;
3124 if( !env )
3125 return NULL;
3127 len = 0;
3128 while ( env[len] )
3129 len += (strlenW(&env[len]) + 1);
3131 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3132 if (!env_copy)
3134 WINE_ERR("out of memory\n");
3135 return env_copy;
3137 memcpy (env_copy, env, len*sizeof (WCHAR));
3138 env_copy[len] = 0;
3140 return env_copy;
3143 /*****************************************************************************
3144 * WCMD_setlocal
3146 * setlocal pushes the environment onto a stack
3147 * Save the environment as unicode so we don't screw anything up.
3149 void WCMD_setlocal (const WCHAR *s) {
3150 WCHAR *env;
3151 struct env_stack *env_copy;
3152 WCHAR cwd[MAX_PATH];
3153 BOOL newdelay;
3154 static const WCHAR ondelayW[] = {'E','N','A','B','L','E','D','E','L','A',
3155 'Y','E','D','E','X','P','A','N','S','I',
3156 'O','N','\0'};
3157 static const WCHAR offdelayW[] = {'D','I','S','A','B','L','E','D','E','L',
3158 'A','Y','E','D','E','X','P','A','N','S',
3159 'I','O','N','\0'};
3161 /* setlocal does nothing outside of batch programs */
3162 if (!context) return;
3164 /* DISABLEEXTENSIONS ignored */
3166 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3167 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3168 if (!strcmpiW(param1, ondelayW) || !strcmpiW(param2, ondelayW)) {
3169 newdelay = TRUE;
3170 } else if (!strcmpiW(param1, offdelayW) || !strcmpiW(param2, offdelayW)) {
3171 newdelay = FALSE;
3172 } else {
3173 newdelay = delayedsubst;
3175 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3177 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3178 if( !env_copy )
3180 WINE_ERR ("out of memory\n");
3181 return;
3184 env = GetEnvironmentStringsW ();
3185 env_copy->strings = WCMD_dupenv (env);
3186 if (env_copy->strings)
3188 env_copy->batchhandle = context->h;
3189 env_copy->next = saved_environment;
3190 env_copy->delayedsubst = delayedsubst;
3191 delayedsubst = newdelay;
3192 saved_environment = env_copy;
3194 /* Save the current drive letter */
3195 GetCurrentDirectoryW(MAX_PATH, cwd);
3196 env_copy->u.cwd = cwd[0];
3198 else
3199 LocalFree (env_copy);
3201 FreeEnvironmentStringsW (env);
3205 /*****************************************************************************
3206 * WCMD_endlocal
3208 * endlocal pops the environment off a stack
3209 * Note: When searching for '=', search from WCHAR position 1, to handle
3210 * special internal environment variables =C:, =D: etc
3212 void WCMD_endlocal (void) {
3213 WCHAR *env, *old, *p;
3214 struct env_stack *temp;
3215 int len, n;
3217 /* setlocal does nothing outside of batch programs */
3218 if (!context) return;
3220 /* setlocal needs a saved environment from within the same context (batch
3221 program) as it was saved in */
3222 if (!saved_environment || saved_environment->batchhandle != context->h)
3223 return;
3225 /* pop the old environment from the stack */
3226 temp = saved_environment;
3227 saved_environment = temp->next;
3229 /* delete the current environment, totally */
3230 env = GetEnvironmentStringsW ();
3231 old = WCMD_dupenv (env);
3232 len = 0;
3233 while (old[len]) {
3234 n = strlenW(&old[len]) + 1;
3235 p = strchrW(&old[len] + 1, '=');
3236 if (p)
3238 *p++ = 0;
3239 SetEnvironmentVariableW (&old[len], NULL);
3241 len += n;
3243 LocalFree (old);
3244 FreeEnvironmentStringsW (env);
3246 /* restore old environment */
3247 env = temp->strings;
3248 len = 0;
3249 delayedsubst = temp->delayedsubst;
3250 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3251 while (env[len]) {
3252 n = strlenW(&env[len]) + 1;
3253 p = strchrW(&env[len] + 1, '=');
3254 if (p)
3256 *p++ = 0;
3257 SetEnvironmentVariableW (&env[len], p);
3259 len += n;
3262 /* Restore current drive letter */
3263 if (IsCharAlphaW(temp->u.cwd)) {
3264 WCHAR envvar[4];
3265 WCHAR cwd[MAX_PATH];
3266 static const WCHAR fmt[] = {'=','%','c',':','\0'};
3268 wsprintfW(envvar, fmt, temp->u.cwd);
3269 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3270 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3271 SetCurrentDirectoryW(cwd);
3275 LocalFree (env);
3276 LocalFree (temp);
3279 /*****************************************************************************
3280 * WCMD_setshow_default
3282 * Set/Show the current default directory
3285 void WCMD_setshow_default (const WCHAR *args) {
3287 BOOL status;
3288 WCHAR string[1024];
3289 WCHAR cwd[1024];
3290 WCHAR *pos;
3291 WIN32_FIND_DATAW fd;
3292 HANDLE hff;
3293 static const WCHAR parmD[] = {'/','D','\0'};
3295 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3297 /* Skip /D and trailing whitespace if on the front of the command line */
3298 if (CompareStringW(LOCALE_USER_DEFAULT,
3299 NORM_IGNORECASE | SORT_STRINGSORT,
3300 args, 2, parmD, -1) == CSTR_EQUAL) {
3301 args += 2;
3302 while (*args && (*args==' ' || *args=='\t'))
3303 args++;
3306 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3307 if (strlenW(args) == 0) {
3308 strcatW (cwd, newlineW);
3309 WCMD_output_asis (cwd);
3311 else {
3312 /* Remove any double quotes, which may be in the
3313 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3314 pos = string;
3315 while (*args) {
3316 if (*args != '"') *pos++ = *args;
3317 args++;
3319 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3320 pos--;
3321 *pos = 0x00;
3323 /* Search for appropriate directory */
3324 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3325 hff = FindFirstFileW(string, &fd);
3326 if (hff != INVALID_HANDLE_VALUE) {
3327 do {
3328 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3329 WCHAR fpath[MAX_PATH];
3330 WCHAR drive[10];
3331 WCHAR dir[MAX_PATH];
3332 WCHAR fname[MAX_PATH];
3333 WCHAR ext[MAX_PATH];
3334 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3336 /* Convert path into actual directory spec */
3337 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3338 WCMD_splitpath(fpath, drive, dir, fname, ext);
3340 /* Rebuild path */
3341 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3342 break;
3344 } while (FindNextFileW(hff, &fd) != 0);
3345 FindClose(hff);
3348 /* Change to that directory */
3349 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3351 status = SetCurrentDirectoryW(string);
3352 if (!status) {
3353 errorlevel = 1;
3354 WCMD_print_error ();
3355 return;
3356 } else {
3358 /* Save away the actual new directory, to store as current location */
3359 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3361 /* Restore old directory if drive letter would change, and
3362 CD x:\directory /D (or pushd c:\directory) not supplied */
3363 if ((strstrW(quals, parmD) == NULL) &&
3364 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3365 SetCurrentDirectoryW(cwd);
3369 /* Set special =C: type environment variable, for drive letter of
3370 change of directory, even if path was restored due to missing
3371 /D (allows changing drive letter when not resident on that
3372 drive */
3373 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3374 WCHAR env[4];
3375 strcpyW(env, equalW);
3376 memcpy(env+1, string, 2 * sizeof(WCHAR));
3377 env[3] = 0x00;
3378 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3379 SetEnvironmentVariableW(env, string);
3383 return;
3386 /****************************************************************************
3387 * WCMD_setshow_date
3389 * Set/Show the system date
3390 * FIXME: Can't change date yet
3393 void WCMD_setshow_date (void) {
3395 WCHAR curdate[64], buffer[64];
3396 DWORD count;
3397 static const WCHAR parmT[] = {'/','T','\0'};
3399 if (strlenW(param1) == 0) {
3400 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3401 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3402 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3403 if (strstrW (quals, parmT) == NULL) {
3404 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3405 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3406 if (count > 2) {
3407 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3411 else WCMD_print_error ();
3413 else {
3414 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3418 /****************************************************************************
3419 * WCMD_compare
3420 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3421 * the equals sign.
3423 static int WCMD_compare( const void *a, const void *b )
3425 int r;
3426 const WCHAR * const *str_a = a, * const *str_b = b;
3427 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3428 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3429 if( r == CSTR_LESS_THAN ) return -1;
3430 if( r == CSTR_GREATER_THAN ) return 1;
3431 return 0;
3434 /****************************************************************************
3435 * WCMD_setshow_sortenv
3437 * sort variables into order for display
3438 * Optionally only display those who start with a stub
3439 * returns the count displayed
3441 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3443 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3444 const WCHAR **str;
3446 if (stub) stublen = strlenW(stub);
3448 /* count the number of strings, and the total length */
3449 while ( s[len] ) {
3450 len += (strlenW(&s[len]) + 1);
3451 count++;
3454 /* add the strings to an array */
3455 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3456 if( !str )
3457 return 0;
3458 str[0] = s;
3459 for( i=1; i<count; i++ )
3460 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3462 /* sort the array */
3463 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3465 /* print it */
3466 for( i=0; i<count; i++ ) {
3467 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3468 NORM_IGNORECASE | SORT_STRINGSORT,
3469 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3470 /* Don't display special internal variables */
3471 if (str[i][0] != '=') {
3472 WCMD_output_asis(str[i]);
3473 WCMD_output_asis(newlineW);
3474 displayedcount++;
3479 LocalFree( str );
3480 return displayedcount;
3483 /****************************************************************************
3484 * WCMD_getprecedence
3485 * Return the precedence of a particular operator
3487 static int WCMD_getprecedence(const WCHAR in)
3489 switch (in) {
3490 case '!':
3491 case '~':
3492 case OP_POSITIVE:
3493 case OP_NEGATIVE:
3494 return 8;
3495 case '*':
3496 case '/':
3497 case '%':
3498 return 7;
3499 case '+':
3500 case '-':
3501 return 6;
3502 case '<':
3503 case '>':
3504 return 5;
3505 case '&':
3506 return 4;
3507 case '^':
3508 return 3;
3509 case '|':
3510 return 2;
3511 case '=':
3512 case OP_ASSSIGNMUL:
3513 case OP_ASSSIGNDIV:
3514 case OP_ASSSIGNMOD:
3515 case OP_ASSSIGNADD:
3516 case OP_ASSSIGNSUB:
3517 case OP_ASSSIGNAND:
3518 case OP_ASSSIGNNOT:
3519 case OP_ASSSIGNOR:
3520 case OP_ASSSIGNSHL:
3521 case OP_ASSSIGNSHR:
3522 return 1;
3523 default:
3524 return 0;
3528 /****************************************************************************
3529 * WCMD_pushnumber
3530 * Push either a number or name (environment variable) onto the supplied
3531 * stack
3533 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3534 VARSTACK *thisstack = heap_alloc(sizeof(VARSTACK));
3535 thisstack->isnum = (var == NULL);
3536 if (var) {
3537 thisstack->variable = var;
3538 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3539 } else {
3540 thisstack->value = num;
3541 WINE_TRACE("Pushed number %d\n", num);
3543 thisstack->next = *varstack;
3544 *varstack = thisstack;
3547 /****************************************************************************
3548 * WCMD_peeknumber
3549 * Returns the value of the top number or environment variable on the stack
3550 * and leaves the item on the stack.
3552 static int WCMD_peeknumber(VARSTACK **varstack) {
3553 int result = 0;
3554 VARSTACK *thisvar;
3556 if (varstack) {
3557 thisvar = *varstack;
3558 if (!thisvar->isnum) {
3559 WCHAR tmpstr[MAXSTRING];
3560 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3561 result = strtoulW(tmpstr,NULL,0);
3563 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3564 } else {
3565 result = thisvar->value;
3568 WINE_TRACE("Peeked number %d\n", result);
3569 return result;
3572 /****************************************************************************
3573 * WCMD_popnumber
3574 * Returns the value of the top number or environment variable on the stack
3575 * and removes the item from the stack.
3577 static int WCMD_popnumber(VARSTACK **varstack) {
3578 int result = 0;
3579 VARSTACK *thisvar;
3581 if (varstack) {
3582 thisvar = *varstack;
3583 result = WCMD_peeknumber(varstack);
3584 if (!thisvar->isnum) heap_free(thisvar->variable);
3585 *varstack = thisvar->next;
3586 heap_free(thisvar);
3588 WINE_TRACE("Popped number %d\n", result);
3589 return result;
3592 /****************************************************************************
3593 * WCMD_pushoperator
3594 * Push an operator onto the supplied stack
3596 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3597 OPSTACK *thisstack = heap_alloc(sizeof(OPSTACK));
3598 thisstack->precedence = precedence;
3599 thisstack->op = op;
3600 thisstack->next = *opstack;
3601 WINE_TRACE("Pushed operator %c\n", op);
3602 *opstack = thisstack;
3605 /****************************************************************************
3606 * WCMD_popoperator
3607 * Returns the operator from the top of the stack and removes the item from
3608 * the stack.
3610 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3611 WCHAR result = 0;
3612 OPSTACK *thisop;
3614 if (opstack) {
3615 thisop = *opstack;
3616 result = thisop->op;
3617 *opstack = thisop->next;
3618 heap_free(thisop);
3620 WINE_TRACE("Popped operator %c\n", result);
3621 return result;
3624 /****************************************************************************
3625 * WCMD_reduce
3626 * Actions the top operator on the stack against the first and sometimes
3627 * second value on the variable stack, and pushes the result
3628 * Returns non-zero on error.
3630 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3631 OPSTACK *thisop;
3632 int var1,var2;
3633 int rc = 0;
3635 if (!*opstack || !*varstack) {
3636 WINE_TRACE("No operators for the reduce\n");
3637 return WCMD_NOOPERATOR;
3640 /* Remove the top operator */
3641 thisop = *opstack;
3642 *opstack = (*opstack)->next;
3643 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop->op);
3645 /* One variable operators */
3646 var1 = WCMD_popnumber(varstack);
3647 switch (thisop->op) {
3648 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3649 break;
3650 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3651 break;
3652 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3653 break;
3654 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3655 break;
3658 /* Two variable operators */
3659 if (!*varstack) {
3660 WINE_TRACE("No operands left for the reduce?\n");
3661 return WCMD_NOOPERAND;
3663 switch (thisop->op) {
3664 case '!':
3665 case '~':
3666 case OP_POSITIVE:
3667 case OP_NEGATIVE:
3668 break; /* Handled above */
3669 case '*': var2 = WCMD_popnumber(varstack);
3670 WCMD_pushnumber(NULL, var2*var1, varstack);
3671 break;
3672 case '/': var2 = WCMD_popnumber(varstack);
3673 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3674 WCMD_pushnumber(NULL, var2/var1, varstack);
3675 break;
3676 case '+': var2 = WCMD_popnumber(varstack);
3677 WCMD_pushnumber(NULL, var2+var1, varstack);
3678 break;
3679 case '-': var2 = WCMD_popnumber(varstack);
3680 WCMD_pushnumber(NULL, var2-var1, varstack);
3681 break;
3682 case '&': var2 = WCMD_popnumber(varstack);
3683 WCMD_pushnumber(NULL, var2&var1, varstack);
3684 break;
3685 case '%': var2 = WCMD_popnumber(varstack);
3686 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3687 WCMD_pushnumber(NULL, var2%var1, varstack);
3688 break;
3689 case '^': var2 = WCMD_popnumber(varstack);
3690 WCMD_pushnumber(NULL, var2^var1, varstack);
3691 break;
3692 case '<': var2 = WCMD_popnumber(varstack);
3693 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3694 which differs from the compiler (for example gcc) so being explicit. */
3695 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3696 WCMD_pushnumber(NULL, 0, varstack);
3697 } else {
3698 WCMD_pushnumber(NULL, var2<<var1, varstack);
3700 break;
3701 case '>': var2 = WCMD_popnumber(varstack);
3702 WCMD_pushnumber(NULL, var2>>var1, varstack);
3703 break;
3704 case '|': var2 = WCMD_popnumber(varstack);
3705 WCMD_pushnumber(NULL, var2|var1, varstack);
3706 break;
3708 case OP_ASSSIGNMUL:
3709 case OP_ASSSIGNDIV:
3710 case OP_ASSSIGNMOD:
3711 case OP_ASSSIGNADD:
3712 case OP_ASSSIGNSUB:
3713 case OP_ASSSIGNAND:
3714 case OP_ASSSIGNNOT:
3715 case OP_ASSSIGNOR:
3716 case OP_ASSSIGNSHL:
3717 case OP_ASSSIGNSHR:
3719 int i = 0;
3721 /* The left of an equals must be one variable */
3722 if (!(*varstack) || (*varstack)->isnum) {
3723 return WCMD_NOOPERAND;
3726 /* Make the number stack grow by inserting the value of the variable */
3727 var2 = WCMD_peeknumber(varstack);
3728 WCMD_pushnumber(NULL, var2, varstack);
3729 WCMD_pushnumber(NULL, var1, varstack);
3731 /* Make the operand stack grow by pushing the assign operator plus the
3732 operator to perform */
3733 while (calcassignments[i].op != ' ' &&
3734 calcassignments[i].calculatedop != thisop->op) {
3735 i++;
3737 if (calcassignments[i].calculatedop == ' ') {
3738 WINE_ERR("Unexpected operator %c\n", thisop->op);
3739 return WCMD_NOOPERATOR;
3741 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3742 WCMD_pushoperator(calcassignments[i].op,
3743 WCMD_getprecedence(calcassignments[i].op), opstack);
3744 break;
3747 case '=':
3749 WCHAR intFormat[] = {'%','d','\0'};
3750 WCHAR result[MAXSTRING];
3752 /* Build the result, then push it onto the stack */
3753 sprintfW(result, intFormat, var1);
3754 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3755 wine_dbgstr_w(result));
3756 SetEnvironmentVariableW((*varstack)->variable, result);
3757 var2 = WCMD_popnumber(varstack);
3758 WCMD_pushnumber(NULL, var1, varstack);
3759 break;
3762 default: WINE_ERR("Unrecognized operator %c\n", thisop->op);
3765 heap_free(thisop);
3766 return rc;
3770 /****************************************************************************
3771 * WCMD_handleExpression
3772 * Handles an expression provided to set /a - If it finds brackets, it uses
3773 * recursion to process the parts in brackets.
3775 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3777 static const WCHAR mathDelims[] = {' ','\t','(',')','!','~','-','*','/','%',
3778 '+','<','>','&','^','|','=',',','\0' };
3779 int rc = 0;
3780 WCHAR *pos;
3781 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3782 OPSTACK *opstackhead = NULL;
3783 VARSTACK *varstackhead = NULL;
3784 WCHAR foundhalf = 0;
3786 /* Initialize */
3787 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3788 pos = *expr;
3790 /* Iterate through until whole expression is processed */
3791 while (pos && *pos) {
3792 BOOL treatasnumber;
3794 /* Skip whitespace to get to the next character to process*/
3795 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3796 if (!*pos) goto exprreturn;
3798 /* If we have found anything other than an operator then its a number/variable */
3799 if (strchrW(mathDelims, *pos) == NULL) {
3800 WCHAR *parmstart, *parm, *dupparm;
3801 WCHAR *nextpos;
3803 /* Cannot have an expression with var/number twice, without an operator
3804 in-between, nor or number following a half constructed << or >> operator */
3805 if (lastwasnumber || foundhalf) {
3806 rc = WCMD_NOOPERATOR;
3807 goto exprerrorreturn;
3809 lastwasnumber = TRUE;
3811 if (isdigitW(*pos)) {
3812 /* For a number - just push it onto the stack */
3813 int num = strtoulW(pos, &nextpos, 0);
3814 WCMD_pushnumber(NULL, num, &varstackhead);
3815 pos = nextpos;
3817 /* Verify the number was validly formed */
3818 if (*nextpos && (strchrW(mathDelims, *nextpos) == NULL)) {
3819 rc = WCMD_BADHEXOCT;
3820 goto exprerrorreturn;
3822 } else {
3824 /* For a variable - just push it onto the stack */
3825 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3826 dupparm = heap_strdupW(parm);
3827 WCMD_pushnumber(dupparm, 0, &varstackhead);
3828 pos = parmstart + strlenW(dupparm);
3830 continue;
3833 /* We have found an operator. Some operators are one character, some two, and the minus
3834 and plus signs need special processing as they can be either operators or just influence
3835 the parameter which follows them */
3836 if (foundhalf && (*pos != foundhalf)) {
3837 /* Badly constructed operator pair */
3838 rc = WCMD_NOOPERATOR;
3839 goto exprerrorreturn;
3842 treatasnumber = FALSE; /* We are processing an operand */
3843 switch (*pos) {
3845 /* > and < are special as they are double character operators (and spaces can be between them!)
3846 If we see these for the first time, set a flag, and second time around we continue.
3847 Note these double character operators are stored as just one of the characters on the stack */
3848 case '>':
3849 case '<': if (!foundhalf) {
3850 foundhalf = *pos;
3851 pos++;
3852 break;
3854 /* We have found the rest, so clear up the knowledge of the half completed part and
3855 drop through to normal operator processing */
3856 foundhalf = 0;
3857 /* drop through */
3859 case '=': if (*pos=='=') {
3860 /* = is special cased as if the last was an operator then we may have e.g. += or
3861 *= etc which we need to handle by replacing the operator that is on the stack
3862 with a calculated assignment equivalent */
3863 if (!lastwasnumber && opstackhead) {
3864 int i = 0;
3865 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3866 i++;
3868 if (calcassignments[i].op == ' ') {
3869 rc = WCMD_NOOPERAND;
3870 goto exprerrorreturn;
3871 } else {
3872 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3873 when the general operator handling happens further down. */
3874 *pos = calcassignments[i].calculatedop;
3875 WCMD_popoperator(&opstackhead);
3879 /* Drop though */
3881 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3882 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3883 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
3884 /* drop through */
3885 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
3886 /* drop through */
3888 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
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 '*': /* drop through */
3896 case '|':
3897 /* General code for handling most of the operators - look at the
3898 precedence of the top item on the stack, and see if we need to
3899 action the stack before we push something else onto it. */
3901 int precedence = WCMD_getprecedence(*pos);
3902 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
3903 precedence, !opstackhead?-1:opstackhead->precedence);
3905 /* In general, for things with the same precedence, reduce immediately
3906 except for assignments and unary operators which do not */
3907 while (!rc && opstackhead &&
3908 ((opstackhead->precedence > precedence) ||
3909 ((opstackhead->precedence == precedence) &&
3910 (precedence != 1) && (precedence != 8)))) {
3911 rc = WCMD_reduce(&opstackhead, &varstackhead);
3913 if (rc) goto exprerrorreturn;
3914 WCMD_pushoperator(*pos, precedence, &opstackhead);
3915 pos++;
3916 break;
3919 /* comma means start a new expression, ie calculate what we have */
3920 case ',':
3922 int prevresult = -1;
3923 WINE_TRACE("Found expression delimiter - reducing exising stacks\n");
3924 while (!rc && opstackhead) {
3925 rc = WCMD_reduce(&opstackhead, &varstackhead);
3927 if (rc) goto exprerrorreturn;
3928 /* If we have anything other than one number left, error
3929 otherwise throw the number away */
3930 if (!varstackhead || varstackhead->next) {
3931 rc = WCMD_NOOPERATOR;
3932 goto exprerrorreturn;
3934 prevresult = WCMD_popnumber(&varstackhead);
3935 WINE_TRACE("Expression resolved to %d\n", prevresult);
3936 heap_free(varstackhead);
3937 varstackhead = NULL;
3938 pos++;
3939 break;
3942 /* Open bracket - use iteration to parse the inner expression, then continue */
3943 case '(' : {
3944 int exprresult = 0;
3945 pos++;
3946 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
3947 if (rc) goto exprerrorreturn;
3948 WCMD_pushnumber(NULL, exprresult, &varstackhead);
3949 break;
3952 /* Close bracket - we have finished this depth, calculate and return */
3953 case ')' : {
3954 pos++;
3955 treatasnumber = TRUE; /* Things in brackets result in a number */
3956 if (depth == 0) {
3957 rc = WCMD_BADPAREN;
3958 goto exprerrorreturn;
3960 goto exprreturn;
3963 default:
3964 WINE_ERR("Unrecognized operator %c\n", *pos);
3965 pos++;
3967 lastwasnumber = treatasnumber;
3970 exprreturn:
3971 *expr = pos;
3973 /* We need to reduce until we have a single number (or variable) on the
3974 stack and set the return value to that */
3975 while (!rc && opstackhead) {
3976 rc = WCMD_reduce(&opstackhead, &varstackhead);
3978 if (rc) goto exprerrorreturn;
3980 /* If we have anything other than one number left, error
3981 otherwise throw the number away */
3982 if (!varstackhead || varstackhead->next) {
3983 rc = WCMD_NOOPERATOR;
3984 goto exprerrorreturn;
3987 /* Now get the number (and convert if its just a variable name) */
3988 *ret = WCMD_popnumber(&varstackhead);
3990 exprerrorreturn:
3991 /* Free all remaining memory */
3992 while (opstackhead) WCMD_popoperator(&opstackhead);
3993 while (varstackhead) WCMD_popnumber(&varstackhead);
3995 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
3996 return rc;
3999 /****************************************************************************
4000 * WCMD_setshow_env
4002 * Set/Show the environment variables
4005 void WCMD_setshow_env (WCHAR *s) {
4007 LPVOID env;
4008 WCHAR *p;
4009 int status;
4010 static const WCHAR parmP[] = {'/','P','\0'};
4011 static const WCHAR parmA[] = {'/','A','\0'};
4012 WCHAR string[MAXSTRING];
4014 if (param1[0] == 0x00 && quals[0] == 0x00) {
4015 env = GetEnvironmentStringsW();
4016 WCMD_setshow_sortenv( env, NULL );
4017 return;
4020 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4021 if (CompareStringW(LOCALE_USER_DEFAULT,
4022 NORM_IGNORECASE | SORT_STRINGSORT,
4023 s, 2, parmP, -1) == CSTR_EQUAL) {
4024 DWORD count;
4026 s += 2;
4027 while (*s && (*s==' ' || *s=='\t')) s++;
4028 if (*s=='\"')
4029 WCMD_strip_quotes(s);
4031 /* If no parameter, or no '=' sign, return an error */
4032 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
4033 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4034 return;
4037 /* Output the prompt */
4038 *p++ = '\0';
4039 if (strlenW(p) != 0) WCMD_output_asis(p);
4041 /* Read the reply */
4042 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4043 if (count > 1) {
4044 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4045 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4046 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4047 wine_dbgstr_w(string));
4048 status = SetEnvironmentVariableW(s, string);
4051 /* See if /A supplied, and if so calculate the results of all the expressions */
4052 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4053 NORM_IGNORECASE | SORT_STRINGSORT,
4054 s, 2, parmA, -1) == CSTR_EQUAL) {
4055 /* /A supplied, so evaluate expressions and set variables appropriately */
4056 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4057 /* of the final computation */
4058 int result = 0;
4059 int rc = 0;
4060 WCHAR *thisexpr;
4061 WCHAR *src,*dst;
4063 /* Remove all quotes before doing any calculations */
4064 thisexpr = heap_alloc((strlenW(s+2)+1) * sizeof(WCHAR));
4065 src = s+2;
4066 dst = thisexpr;
4067 while (*src) {
4068 if (*src != '"') *dst++ = *src;
4069 src++;
4071 *dst = 0;
4073 /* Now calculate the results of the expression */
4074 src = thisexpr;
4075 rc = WCMD_handleExpression(&src, &result, 0);
4076 heap_free(thisexpr);
4078 /* If parsing failed, issue the error message */
4079 if (rc > 0) {
4080 WCMD_output_stderr(WCMD_LoadMessage(rc));
4081 return;
4084 /* If we have no context (interactive or cmd.exe /c) print the final result */
4085 if (!context) {
4086 static const WCHAR fmt[] = {'%','d','\0'};
4087 sprintfW(string, fmt, result);
4088 WCMD_output_asis(string);
4091 } else {
4092 DWORD gle;
4094 if (*s=='\"')
4095 WCMD_strip_quotes(s);
4096 p = strchrW (s, '=');
4097 if (p == NULL) {
4098 env = GetEnvironmentStringsW();
4099 if (WCMD_setshow_sortenv( env, s ) == 0) {
4100 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4101 errorlevel = 1;
4103 return;
4105 *p++ = '\0';
4107 if (strlenW(p) == 0) p = NULL;
4108 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4109 wine_dbgstr_w(p));
4110 status = SetEnvironmentVariableW(s, p);
4111 gle = GetLastError();
4112 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4113 errorlevel = 1;
4114 } else if ((!status)) WCMD_print_error();
4115 else errorlevel = 0;
4119 /****************************************************************************
4120 * WCMD_setshow_path
4122 * Set/Show the path environment variable
4125 void WCMD_setshow_path (const WCHAR *args) {
4127 WCHAR string[1024];
4128 DWORD status;
4129 static const WCHAR pathW[] = {'P','A','T','H','\0'};
4130 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
4132 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
4133 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
4134 if (status != 0) {
4135 WCMD_output_asis ( pathEqW);
4136 WCMD_output_asis ( string);
4137 WCMD_output_asis ( newlineW);
4139 else {
4140 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4143 else {
4144 if (*args == '=') args++; /* Skip leading '=' */
4145 status = SetEnvironmentVariableW(pathW, args);
4146 if (!status) WCMD_print_error();
4150 /****************************************************************************
4151 * WCMD_setshow_prompt
4153 * Set or show the command prompt.
4156 void WCMD_setshow_prompt (void) {
4158 WCHAR *s;
4159 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
4161 if (strlenW(param1) == 0) {
4162 SetEnvironmentVariableW(promptW, NULL);
4164 else {
4165 s = param1;
4166 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4167 if (strlenW(s) == 0) {
4168 SetEnvironmentVariableW(promptW, NULL);
4170 else SetEnvironmentVariableW(promptW, s);
4174 /****************************************************************************
4175 * WCMD_setshow_time
4177 * Set/Show the system time
4178 * FIXME: Can't change time yet
4181 void WCMD_setshow_time (void) {
4183 WCHAR curtime[64], buffer[64];
4184 DWORD count;
4185 SYSTEMTIME st;
4186 static const WCHAR parmT[] = {'/','T','\0'};
4188 if (strlenW(param1) == 0) {
4189 GetLocalTime(&st);
4190 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
4191 curtime, sizeof(curtime)/sizeof(WCHAR))) {
4192 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4193 if (strstrW (quals, parmT) == NULL) {
4194 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4195 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4196 if (count > 2) {
4197 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4201 else WCMD_print_error ();
4203 else {
4204 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4208 /****************************************************************************
4209 * WCMD_shift
4211 * Shift batch parameters.
4212 * Optional /n says where to start shifting (n=0-8)
4215 void WCMD_shift (const WCHAR *args) {
4216 int start;
4218 if (context != NULL) {
4219 WCHAR *pos = strchrW(args, '/');
4220 int i;
4222 if (pos == NULL) {
4223 start = 0;
4224 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4225 start = (*(pos+1) - '0');
4226 } else {
4227 SetLastError(ERROR_INVALID_PARAMETER);
4228 WCMD_print_error();
4229 return;
4232 WINE_TRACE("Shifting variables, starting at %d\n", start);
4233 for (i=start;i<=8;i++) {
4234 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4236 context -> shift_count[9] = context -> shift_count[9] + 1;
4241 /****************************************************************************
4242 * WCMD_start
4244 void WCMD_start(const WCHAR *args)
4246 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
4247 '\\','s','t','a','r','t','.','e','x','e',0};
4248 WCHAR file[MAX_PATH];
4249 WCHAR *cmdline;
4250 STARTUPINFOW st;
4251 PROCESS_INFORMATION pi;
4253 GetWindowsDirectoryW( file, MAX_PATH );
4254 strcatW( file, exeW );
4255 cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
4256 strcpyW( cmdline, file );
4257 strcatW( cmdline, spaceW );
4258 strcatW( cmdline, args );
4260 memset( &st, 0, sizeof(STARTUPINFOW) );
4261 st.cb = sizeof(STARTUPINFOW);
4263 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4265 WaitForSingleObject( pi.hProcess, INFINITE );
4266 GetExitCodeProcess( pi.hProcess, &errorlevel );
4267 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4268 CloseHandle(pi.hProcess);
4269 CloseHandle(pi.hThread);
4271 else
4273 SetLastError(ERROR_FILE_NOT_FOUND);
4274 WCMD_print_error ();
4275 errorlevel = 9009;
4277 heap_free(cmdline);
4280 /****************************************************************************
4281 * WCMD_title
4283 * Set the console title
4285 void WCMD_title (const WCHAR *args) {
4286 SetConsoleTitleW(args);
4289 /****************************************************************************
4290 * WCMD_type
4292 * Copy a file to standard output.
4295 void WCMD_type (WCHAR *args) {
4297 int argno = 0;
4298 WCHAR *argN = args;
4299 BOOL writeHeaders = FALSE;
4301 if (param1[0] == 0x00) {
4302 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4303 return;
4306 if (param2[0] != 0x00) writeHeaders = TRUE;
4308 /* Loop through all args */
4309 errorlevel = 0;
4310 while (argN) {
4311 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4313 HANDLE h;
4314 WCHAR buffer[512];
4315 DWORD count;
4317 if (!argN) break;
4319 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4320 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4321 FILE_ATTRIBUTE_NORMAL, NULL);
4322 if (h == INVALID_HANDLE_VALUE) {
4323 WCMD_print_error ();
4324 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4325 errorlevel = 1;
4326 } else {
4327 if (writeHeaders) {
4328 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
4329 WCMD_output(fmt, thisArg);
4331 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
4332 if (count == 0) break; /* ReadFile reports success on EOF! */
4333 buffer[count] = 0;
4334 WCMD_output_asis (buffer);
4336 CloseHandle (h);
4341 /****************************************************************************
4342 * WCMD_more
4344 * Output either a file or stdin to screen in pages
4347 void WCMD_more (WCHAR *args) {
4349 int argno = 0;
4350 WCHAR *argN = args;
4351 WCHAR moreStr[100];
4352 WCHAR moreStrPage[100];
4353 WCHAR buffer[512];
4354 DWORD count;
4355 static const WCHAR moreStart[] = {'-','-',' ','\0'};
4356 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
4357 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
4358 ')',' ','-','-','\n','\0'};
4359 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
4361 /* Prefix the NLS more with '-- ', then load the text */
4362 errorlevel = 0;
4363 strcpyW(moreStr, moreStart);
4364 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
4365 (sizeof(moreStr)/sizeof(WCHAR))-3);
4367 if (param1[0] == 0x00) {
4369 /* Wine implements pipes via temporary files, and hence stdin is
4370 effectively reading from the file. This means the prompts for
4371 more are satisfied by the next line from the input (file). To
4372 avoid this, ensure stdin is to the console */
4373 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4374 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
4375 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4376 FILE_ATTRIBUTE_NORMAL, 0);
4377 WINE_TRACE("No parms - working probably in pipe mode\n");
4378 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4380 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4381 once you get in this bit unless due to a pipe, its going to end badly... */
4382 wsprintfW(moreStrPage, moreFmt, moreStr);
4384 WCMD_enter_paged_mode(moreStrPage);
4385 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4386 if (count == 0) break; /* ReadFile reports success on EOF! */
4387 buffer[count] = 0;
4388 WCMD_output_asis (buffer);
4390 WCMD_leave_paged_mode();
4392 /* Restore stdin to what it was */
4393 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4394 CloseHandle(hConIn);
4396 return;
4397 } else {
4398 BOOL needsPause = FALSE;
4400 /* Loop through all args */
4401 WINE_TRACE("Parms supplied - working through each file\n");
4402 WCMD_enter_paged_mode(moreStrPage);
4404 while (argN) {
4405 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4406 HANDLE h;
4408 if (!argN) break;
4410 if (needsPause) {
4412 /* Wait */
4413 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
4414 WCMD_leave_paged_mode();
4415 WCMD_output_asis(moreStrPage);
4416 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4417 WCMD_enter_paged_mode(moreStrPage);
4421 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4422 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4423 FILE_ATTRIBUTE_NORMAL, NULL);
4424 if (h == INVALID_HANDLE_VALUE) {
4425 WCMD_print_error ();
4426 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4427 errorlevel = 1;
4428 } else {
4429 ULONG64 curPos = 0;
4430 ULONG64 fileLen = 0;
4431 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4433 /* Get the file size */
4434 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4435 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4437 needsPause = TRUE;
4438 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4439 if (count == 0) break; /* ReadFile reports success on EOF! */
4440 buffer[count] = 0;
4441 curPos += count;
4443 /* Update % count (would be used in WCMD_output_asis as prompt) */
4444 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
4446 WCMD_output_asis (buffer);
4448 CloseHandle (h);
4452 WCMD_leave_paged_mode();
4456 /****************************************************************************
4457 * WCMD_verify
4459 * Display verify flag.
4460 * FIXME: We don't actually do anything with the verify flag other than toggle
4461 * it...
4464 void WCMD_verify (const WCHAR *args) {
4466 int count;
4468 count = strlenW(args);
4469 if (count == 0) {
4470 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
4471 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
4472 return;
4474 if (lstrcmpiW(args, onW) == 0) {
4475 verify_mode = TRUE;
4476 return;
4478 else if (lstrcmpiW(args, offW) == 0) {
4479 verify_mode = FALSE;
4480 return;
4482 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4485 /****************************************************************************
4486 * WCMD_version
4488 * Display version info.
4491 void WCMD_version (void) {
4493 WCMD_output_asis (version_string);
4497 /****************************************************************************
4498 * WCMD_volume
4500 * Display volume information (set_label = FALSE)
4501 * Additionally set volume label (set_label = TRUE)
4502 * Returns 1 on success, 0 otherwise
4505 int WCMD_volume(BOOL set_label, const WCHAR *path)
4507 DWORD count, serial;
4508 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4509 BOOL status;
4511 if (strlenW(path) == 0) {
4512 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
4513 if (!status) {
4514 WCMD_print_error ();
4515 return 0;
4517 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
4518 &serial, NULL, NULL, NULL, 0);
4520 else {
4521 static const WCHAR fmt[] = {'%','s','\\','\0'};
4522 if ((path[1] != ':') || (strlenW(path) != 2)) {
4523 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4524 return 0;
4526 wsprintfW (curdir, fmt, path);
4527 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
4528 &serial, NULL,
4529 NULL, NULL, 0);
4531 if (!status) {
4532 WCMD_print_error ();
4533 return 0;
4535 if (label[0] != '\0') {
4536 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4537 curdir[0], label);
4539 else {
4540 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4541 curdir[0]);
4543 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4544 HIWORD(serial), LOWORD(serial));
4545 if (set_label) {
4546 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4547 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4548 if (count > 1) {
4549 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4550 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4552 if (strlenW(path) != 0) {
4553 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4555 else {
4556 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4559 return 1;
4562 /**************************************************************************
4563 * WCMD_exit
4565 * Exit either the process, or just this batch program
4569 void WCMD_exit (CMD_LIST **cmdList) {
4571 static const WCHAR parmB[] = {'/','B','\0'};
4572 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
4574 if (context && lstrcmpiW(quals, parmB) == 0) {
4575 errorlevel = rc;
4576 context -> skip_rest = TRUE;
4577 *cmdList = NULL;
4578 } else {
4579 ExitProcess(rc);
4584 /*****************************************************************************
4585 * WCMD_assoc
4587 * Lists or sets file associations (assoc = TRUE)
4588 * Lists or sets file types (assoc = FALSE)
4590 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4592 HKEY key;
4593 DWORD accessOptions = KEY_READ;
4594 WCHAR *newValue;
4595 LONG rc = ERROR_SUCCESS;
4596 WCHAR keyValue[MAXSTRING];
4597 DWORD valueLen = MAXSTRING;
4598 HKEY readKey;
4599 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
4600 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4602 /* See if parameter includes '=' */
4603 errorlevel = 0;
4604 newValue = strchrW(args, '=');
4605 if (newValue) accessOptions |= KEY_WRITE;
4607 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4608 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
4609 accessOptions, &key) != ERROR_SUCCESS) {
4610 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4611 return;
4614 /* If no parameters then list all associations */
4615 if (*args == 0x00) {
4616 int index = 0;
4618 /* Enumerate all the keys */
4619 while (rc != ERROR_NO_MORE_ITEMS) {
4620 WCHAR keyName[MAXSTRING];
4621 DWORD nameLen;
4623 /* Find the next value */
4624 nameLen = MAXSTRING;
4625 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4627 if (rc == ERROR_SUCCESS) {
4629 /* Only interested in extension ones if assoc, or others
4630 if not assoc */
4631 if ((keyName[0] == '.' && assoc) ||
4632 (!(keyName[0] == '.') && (!assoc)))
4634 WCHAR subkey[MAXSTRING];
4635 strcpyW(subkey, keyName);
4636 if (!assoc) strcatW(subkey, shOpCmdW);
4638 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4640 valueLen = sizeof(keyValue)/sizeof(WCHAR);
4641 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4642 WCMD_output_asis(keyName);
4643 WCMD_output_asis(equalW);
4644 /* If no default value found, leave line empty after '=' */
4645 if (rc == ERROR_SUCCESS) {
4646 WCMD_output_asis(keyValue);
4648 WCMD_output_asis(newlineW);
4649 RegCloseKey(readKey);
4655 } else {
4657 /* Parameter supplied - if no '=' on command line, its a query */
4658 if (newValue == NULL) {
4659 WCHAR *space;
4660 WCHAR subkey[MAXSTRING];
4662 /* Query terminates the parameter at the first space */
4663 strcpyW(keyValue, args);
4664 space = strchrW(keyValue, ' ');
4665 if (space) *space=0x00;
4667 /* Set up key name */
4668 strcpyW(subkey, keyValue);
4669 if (!assoc) strcatW(subkey, shOpCmdW);
4671 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4673 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4674 WCMD_output_asis(args);
4675 WCMD_output_asis(equalW);
4676 /* If no default value found, leave line empty after '=' */
4677 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4678 WCMD_output_asis(newlineW);
4679 RegCloseKey(readKey);
4681 } else {
4682 WCHAR msgbuffer[MAXSTRING];
4684 /* Load the translated 'File association not found' */
4685 if (assoc) {
4686 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4687 } else {
4688 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4690 WCMD_output_stderr(msgbuffer, keyValue);
4691 errorlevel = 2;
4694 /* Not a query - its a set or clear of a value */
4695 } else {
4697 WCHAR subkey[MAXSTRING];
4699 /* Get pointer to new value */
4700 *newValue = 0x00;
4701 newValue++;
4703 /* Set up key name */
4704 strcpyW(subkey, args);
4705 if (!assoc) strcatW(subkey, shOpCmdW);
4707 /* If nothing after '=' then clear value - only valid for ASSOC */
4708 if (*newValue == 0x00) {
4710 if (assoc) rc = RegDeleteKeyW(key, args);
4711 if (assoc && rc == ERROR_SUCCESS) {
4712 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4714 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4715 WCMD_print_error();
4716 errorlevel = 2;
4718 } else {
4719 WCHAR msgbuffer[MAXSTRING];
4721 /* Load the translated 'File association not found' */
4722 if (assoc) {
4723 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
4724 sizeof(msgbuffer)/sizeof(WCHAR));
4725 } else {
4726 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
4727 sizeof(msgbuffer)/sizeof(WCHAR));
4729 WCMD_output_stderr(msgbuffer, keyValue);
4730 errorlevel = 2;
4733 /* It really is a set value = contents */
4734 } else {
4735 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4736 accessOptions, NULL, &readKey, NULL);
4737 if (rc == ERROR_SUCCESS) {
4738 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4739 (LPBYTE)newValue,
4740 sizeof(WCHAR) * (strlenW(newValue) + 1));
4741 RegCloseKey(readKey);
4744 if (rc != ERROR_SUCCESS) {
4745 WCMD_print_error();
4746 errorlevel = 2;
4747 } else {
4748 WCMD_output_asis(args);
4749 WCMD_output_asis(equalW);
4750 WCMD_output_asis(newValue);
4751 WCMD_output_asis(newlineW);
4757 /* Clean up */
4758 RegCloseKey(key);
4761 /****************************************************************************
4762 * WCMD_color
4764 * Colors the terminal screen.
4767 void WCMD_color (void) {
4769 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4770 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4772 if (param1[0] != 0x00 && strlenW(param1) > 2) {
4773 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4774 return;
4777 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4779 COORD topLeft;
4780 DWORD screenSize;
4781 DWORD color = 0;
4783 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4785 topLeft.X = 0;
4786 topLeft.Y = 0;
4788 /* Convert the color hex digits */
4789 if (param1[0] == 0x00) {
4790 color = defaultColor;
4791 } else {
4792 color = strtoulW(param1, NULL, 16);
4795 /* Fail if fg == bg color */
4796 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4797 errorlevel = 1;
4798 return;
4801 /* Set the current screen contents and ensure all future writes
4802 remain this color */
4803 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4804 SetConsoleTextAttribute(hStdOut, color);