cmd: Make WCMD_forf_getinputhandle() static.
[wine.git] / programs / cmd / builtins.c
blobe567bb64f28b964ab8a266d42f7bf50e21524379
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 inbuilt[][10] = {
51 {'C','A','L','L','\0'},
52 {'C','D','\0'},
53 {'C','H','D','I','R','\0'},
54 {'C','L','S','\0'},
55 {'C','O','P','Y','\0'},
56 {'C','T','T','Y','\0'},
57 {'D','A','T','E','\0'},
58 {'D','E','L','\0'},
59 {'D','I','R','\0'},
60 {'E','C','H','O','\0'},
61 {'E','R','A','S','E','\0'},
62 {'F','O','R','\0'},
63 {'G','O','T','O','\0'},
64 {'H','E','L','P','\0'},
65 {'I','F','\0'},
66 {'L','A','B','E','L','\0'},
67 {'M','D','\0'},
68 {'M','K','D','I','R','\0'},
69 {'M','O','V','E','\0'},
70 {'P','A','T','H','\0'},
71 {'P','A','U','S','E','\0'},
72 {'P','R','O','M','P','T','\0'},
73 {'R','E','M','\0'},
74 {'R','E','N','\0'},
75 {'R','E','N','A','M','E','\0'},
76 {'R','D','\0'},
77 {'R','M','D','I','R','\0'},
78 {'S','E','T','\0'},
79 {'S','H','I','F','T','\0'},
80 {'S','T','A','R','T','\0'},
81 {'T','I','M','E','\0'},
82 {'T','I','T','L','E','\0'},
83 {'T','Y','P','E','\0'},
84 {'V','E','R','I','F','Y','\0'},
85 {'V','E','R','\0'},
86 {'V','O','L','\0'},
87 {'E','N','D','L','O','C','A','L','\0'},
88 {'S','E','T','L','O','C','A','L','\0'},
89 {'P','U','S','H','D','\0'},
90 {'P','O','P','D','\0'},
91 {'A','S','S','O','C','\0'},
92 {'C','O','L','O','R','\0'},
93 {'F','T','Y','P','E','\0'},
94 {'M','O','R','E','\0'},
95 {'C','H','O','I','C','E','\0'},
96 {'E','X','I','T','\0'}
98 static const WCHAR externals[][10] = {
99 {'A','T','T','R','I','B','\0'},
100 {'X','C','O','P','Y','\0'}
102 static const WCHAR fslashW[] = {'/','\0'};
103 static const WCHAR onW[] = {'O','N','\0'};
104 static const WCHAR offW[] = {'O','F','F','\0'};
105 static const WCHAR parmY[] = {'/','Y','\0'};
106 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
107 static const WCHAR eqeqW[] = {'=','=','\0'};
109 static HINSTANCE hinst;
110 struct env_stack *saved_environment;
111 static BOOL verify_mode = FALSE;
113 /**************************************************************************
114 * WCMD_ask_confirm
116 * Issue a message and ask for confirmation, waiting on a valid answer.
118 * Returns True if Y (or A) answer is selected
119 * If optionAll contains a pointer, ALL is allowed, and if answered
120 * set to TRUE
123 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
124 BOOL *optionAll) {
126 UINT msgid;
127 WCHAR confirm[MAXSTRING];
128 WCHAR options[MAXSTRING];
129 WCHAR Ybuffer[MAXSTRING];
130 WCHAR Nbuffer[MAXSTRING];
131 WCHAR Abuffer[MAXSTRING];
132 WCHAR answer[MAX_PATH] = {'\0'};
133 DWORD count = 0;
135 /* Load the translated valid answers */
136 if (showSureText)
137 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
138 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
139 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
140 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
141 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
142 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
144 /* Loop waiting on a valid answer */
145 if (optionAll)
146 *optionAll = FALSE;
147 while (1)
149 WCMD_output_asis (message);
150 if (showSureText)
151 WCMD_output_asis (confirm);
152 WCMD_output_asis (options);
153 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
154 answer[0] = toupperW(answer[0]);
155 if (answer[0] == Ybuffer[0])
156 return TRUE;
157 if (answer[0] == Nbuffer[0])
158 return FALSE;
159 if (optionAll && answer[0] == Abuffer[0])
161 *optionAll = TRUE;
162 return TRUE;
167 /****************************************************************************
168 * WCMD_clear_screen
170 * Clear the terminal screen.
173 void WCMD_clear_screen (void) {
175 /* Emulate by filling the screen from the top left to bottom right with
176 spaces, then moving the cursor to the top left afterwards */
177 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
178 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
180 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
182 COORD topLeft;
183 DWORD screenSize;
185 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
187 topLeft.X = 0;
188 topLeft.Y = 0;
189 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
190 SetConsoleCursorPosition(hStdOut, topLeft);
194 /****************************************************************************
195 * WCMD_change_tty
197 * Change the default i/o device (ie redirect STDin/STDout).
200 void WCMD_change_tty (void) {
202 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
206 /****************************************************************************
207 * WCMD_choice
211 void WCMD_choice (const WCHAR * args) {
213 static const WCHAR bellW[] = {7,0};
214 static const WCHAR commaW[] = {',',0};
215 static const WCHAR bracket_open[] = {'[',0};
216 static const WCHAR bracket_close[] = {']','?',0};
217 WCHAR answer[16];
218 WCHAR buffer[16];
219 WCHAR *ptr = NULL;
220 WCHAR *opt_c = NULL;
221 WCHAR *my_command = NULL;
222 WCHAR opt_default = 0;
223 DWORD opt_timeout = 0;
224 DWORD count;
225 DWORD oldmode;
226 DWORD have_console;
227 BOOL opt_n = FALSE;
228 BOOL opt_s = FALSE;
230 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
231 errorlevel = 0;
233 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
234 if (!my_command)
235 return;
237 ptr = WCMD_skip_leading_spaces(my_command);
238 while (*ptr == '/') {
239 switch (toupperW(ptr[1])) {
240 case 'C':
241 ptr += 2;
242 /* the colon is optional */
243 if (*ptr == ':')
244 ptr++;
246 if (!*ptr || isspaceW(*ptr)) {
247 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
248 HeapFree(GetProcessHeap(), 0, my_command);
249 return;
252 /* remember the allowed keys (overwrite previous /C option) */
253 opt_c = ptr;
254 while (*ptr && (!isspaceW(*ptr)))
255 ptr++;
257 if (*ptr) {
258 /* terminate allowed chars */
259 *ptr = 0;
260 ptr = WCMD_skip_leading_spaces(&ptr[1]);
262 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
263 break;
265 case 'N':
266 opt_n = TRUE;
267 ptr = WCMD_skip_leading_spaces(&ptr[2]);
268 break;
270 case 'S':
271 opt_s = TRUE;
272 ptr = WCMD_skip_leading_spaces(&ptr[2]);
273 break;
275 case 'T':
276 ptr = &ptr[2];
277 /* the colon is optional */
278 if (*ptr == ':')
279 ptr++;
281 opt_default = *ptr++;
283 if (!opt_default || (*ptr != ',')) {
284 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
285 HeapFree(GetProcessHeap(), 0, my_command);
286 return;
288 ptr++;
290 count = 0;
291 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
292 count++;
293 ptr++;
296 answer[count] = 0;
297 opt_timeout = atoiW(answer);
299 ptr = WCMD_skip_leading_spaces(ptr);
300 break;
302 default:
303 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
304 HeapFree(GetProcessHeap(), 0, my_command);
305 return;
309 if (opt_timeout)
310 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
312 if (have_console)
313 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
315 /* use default keys, when needed: localized versions of "Y"es and "No" */
316 if (!opt_c) {
317 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
318 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
319 opt_c = buffer;
320 buffer[2] = 0;
323 /* print the question, when needed */
324 if (*ptr)
325 WCMD_output_asis(ptr);
327 if (!opt_s) {
328 struprW(opt_c);
329 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
332 if (!opt_n) {
333 /* print a list of all allowed answers inside brackets */
334 WCMD_output_asis(bracket_open);
335 ptr = opt_c;
336 answer[1] = 0;
337 while ((answer[0] = *ptr++)) {
338 WCMD_output_asis(answer);
339 if (*ptr)
340 WCMD_output_asis(commaW);
342 WCMD_output_asis(bracket_close);
345 while (TRUE) {
347 /* FIXME: Add support for option /T */
348 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
350 if (!opt_s)
351 answer[0] = toupperW(answer[0]);
353 ptr = strchrW(opt_c, answer[0]);
354 if (ptr) {
355 WCMD_output_asis(answer);
356 WCMD_output_asis(newlineW);
357 if (have_console)
358 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
360 errorlevel = (ptr - opt_c) + 1;
361 WINE_TRACE("answer: %d\n", errorlevel);
362 HeapFree(GetProcessHeap(), 0, my_command);
363 return;
365 else
367 /* key not allowed: play the bell */
368 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
369 WCMD_output_asis(bellW);
374 /****************************************************************************
375 * WCMD_AppendEOF
377 * Adds an EOF onto the end of a file
378 * Returns TRUE on success
380 static BOOL WCMD_AppendEOF(WCHAR *filename)
382 HANDLE h;
384 char eof = '\x1a';
386 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
387 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
388 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
390 if (h == NULL) {
391 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
392 return FALSE;
393 } else {
394 SetFilePointer (h, 0, NULL, FILE_END);
395 if (!WriteFile(h, &eof, 1, NULL, NULL)) {
396 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
397 return FALSE;
399 CloseHandle(h);
401 return TRUE;
404 /****************************************************************************
405 * WCMD_ManualCopy
407 * Copies from a file
408 * optionally reading only until EOF (ascii copy)
409 * optionally appending onto an existing file (append)
410 * Returns TRUE on success
412 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
414 HANDLE in,out;
415 BOOL ok;
416 DWORD bytesread, byteswritten;
418 WINE_TRACE("ASCII Copying %s to %s (append?%d)\n",
419 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
421 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
422 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
423 if (in == NULL) {
424 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
425 return FALSE;
428 /* Open the output file, overwriting if not appending */
429 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
430 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
431 if (out == NULL) {
432 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
433 return FALSE;
436 /* Move to end of destination if we are going to append to it */
437 if (append) {
438 SetFilePointer(out, 0, NULL, FILE_END);
441 /* Loop copying data from source to destination until EOF read */
442 ok = TRUE;
445 char buffer[MAXSTRING];
447 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
448 if (ok) {
450 /* Stop at first EOF */
451 if (ascii) {
452 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
453 if (ptr) bytesread = (ptr - buffer);
456 if (bytesread) {
457 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
458 if (!ok || byteswritten != bytesread) {
459 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
460 wine_dbgstr_w(dstname), GetLastError());
463 } else {
464 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
465 wine_dbgstr_w(srcname), GetLastError());
467 } while (ok && bytesread > 0);
469 CloseHandle(out);
470 CloseHandle(in);
471 return ok;
474 /****************************************************************************
475 * WCMD_copy
477 * Copy a file or wildcarded set.
478 * For ascii/binary type copies, it gets complex:
479 * Syntax on command line is
480 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
481 * Where first /a or /b sets 'mode in operation' until another is found
482 * once another is found, it applies to the file preceding the /a or /b
483 * In addition each filename can contain wildcards
484 * To make matters worse, the + may be in the same parameter (i.e. no
485 * whitespace) or with whitespace separating it
487 * ASCII mode on read == read and stop at first EOF
488 * ASCII mode on write == append EOF to destination
489 * Binary == copy as-is
491 * Design of this is to build up a list of files which will be copied into a
492 * list, then work through the list file by file.
493 * If no destination is specified, it defaults to the name of the first file in
494 * the list, but the current directory.
498 void WCMD_copy(WCHAR * args) {
500 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
501 WCHAR *thisparam;
502 int argno = 0;
503 WCHAR *rawarg;
504 WIN32_FIND_DATAW fd;
505 HANDLE hff;
506 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
507 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
508 BOOL anyconcats = FALSE; /* Have we found any + options */
509 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
510 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
511 BOOL prompt; /* Prompt before overwriting */
512 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
513 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
514 BOOL status;
515 WCHAR copycmd[4];
516 DWORD len;
517 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
519 typedef struct _COPY_FILES
521 struct _COPY_FILES *next;
522 BOOL concatenate;
523 WCHAR *name;
524 int binarycopy;
525 } COPY_FILES;
526 COPY_FILES *sourcelist = NULL;
527 COPY_FILES *lastcopyentry = NULL;
528 COPY_FILES *destination = NULL;
529 COPY_FILES *thiscopy = NULL;
530 COPY_FILES *prevcopy = NULL;
532 /* Assume we were successful! */
533 errorlevel = 0;
535 /* If no args supplied at all, report an error */
536 if (param1[0] == 0x00) {
537 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
538 errorlevel = 1;
539 return;
542 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
544 /* Walk through all args, building up a list of files to process */
545 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
546 while (*(thisparam)) {
547 WCHAR *pos1, *pos2;
548 BOOL inquotes;
550 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
552 /* Handle switches */
553 if (*thisparam == '/') {
554 while (*thisparam == '/') {
555 thisparam++;
556 if (toupperW(*thisparam) == 'D') {
557 opt_d = TRUE;
558 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
559 } else if (toupperW(*thisparam) == 'Y') {
560 opt_y = TRUE;
561 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
562 opt_noty = TRUE;
563 } else if (toupperW(*thisparam) == 'V') {
564 opt_v = TRUE;
565 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
566 } else if (toupperW(*thisparam) == 'N') {
567 opt_n = TRUE;
568 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
569 } else if (toupperW(*thisparam) == 'Z') {
570 opt_z = TRUE;
571 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
572 } else if (toupperW(*thisparam) == 'A') {
573 if (binarymode != 0) {
574 binarymode = 0;
575 WINE_TRACE("Subsequent files will be handled as ASCII\n");
576 if (destination != NULL) {
577 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
578 destination->binarycopy = binarymode;
579 } else if (lastcopyentry != NULL) {
580 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
581 lastcopyentry->binarycopy = binarymode;
584 } else if (toupperW(*thisparam) == 'B') {
585 if (binarymode != 1) {
586 binarymode = 1;
587 WINE_TRACE("Subsequent files will be handled as binary\n");
588 if (destination != NULL) {
589 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
590 destination->binarycopy = binarymode;
591 } else if (lastcopyentry != NULL) {
592 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
593 lastcopyentry->binarycopy = binarymode;
596 } else {
597 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
599 thisparam++;
602 /* This parameter was purely switches, get the next one */
603 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
604 continue;
607 /* We have found something which is not a switch. If could be anything of the form
608 sourcefilename (which could be destination too)
609 + (when filename + filename syntex used)
610 sourcefilename+sourcefilename
611 +sourcefilename
612 +/b[tests show windows then ignores to end of parameter]
615 if (*thisparam=='+') {
616 if (lastcopyentry == NULL) {
617 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
618 errorlevel = 1;
619 goto exitreturn;
620 } else {
621 concatnextfilename = TRUE;
622 anyconcats = TRUE;
625 /* Move to next thing to process */
626 thisparam++;
627 if (*thisparam == 0x00)
628 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
629 continue;
632 /* We have found something to process - build a COPY_FILE block to store it */
633 thiscopy = HeapAlloc(GetProcessHeap(),0,sizeof(COPY_FILES));
634 if (thiscopy == NULL) goto exitreturn;
637 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
638 thiscopy->concatenate = concatnextfilename;
639 thiscopy->binarycopy = binarymode;
640 thiscopy->next = NULL;
642 /* Time to work out the name. Allocate at least enough space (deliberately too much to
643 leave space to append \* to the end) , then copy in character by character. Strip off
644 quotes if we find them. */
645 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
646 thiscopy->name = HeapAlloc(GetProcessHeap(),0,len*sizeof(WCHAR));
647 memset(thiscopy->name, 0x00, len);
649 pos1 = thisparam;
650 pos2 = thiscopy->name;
651 inquotes = FALSE;
652 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
653 if (*pos1 == '"') {
654 inquotes = !inquotes;
655 pos1++;
656 } else *pos2++ = *pos1++;
658 *pos2 = 0;
659 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
661 /* This is either the first source, concatenated subsequent source or destination */
662 if (sourcelist == NULL) {
663 WINE_TRACE("Adding as first source part\n");
664 sourcelist = thiscopy;
665 lastcopyentry = thiscopy;
666 } else if (concatnextfilename) {
667 WINE_TRACE("Adding to source file list to be concatenated\n");
668 lastcopyentry->next = thiscopy;
669 lastcopyentry = thiscopy;
670 } else if (destination == NULL) {
671 destination = thiscopy;
672 } else {
673 /* We have processed sources and destinations and still found more to do - invalid */
674 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
675 errorlevel = 1;
676 goto exitreturn;
678 concatnextfilename = FALSE;
680 /* We either need to process the rest of the parameter or move to the next */
681 if (*pos1 == '/' || *pos1 == '+') {
682 thisparam = pos1;
683 continue;
684 } else {
685 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
689 /* Ensure we have at least one source file */
690 if (!sourcelist) {
691 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
692 errorlevel = 1;
693 goto exitreturn;
696 /* Default whether automatic overwriting is on. If we are interactive then
697 we prompt by default, otherwise we overwrite by default
698 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
699 if (opt_noty) prompt = TRUE;
700 else if (opt_y) prompt = FALSE;
701 else {
702 /* By default, we will force the overwrite in batch mode and ask for
703 * confirmation in interactive mode. */
704 prompt = interactive;
705 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
706 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
707 * default behavior. */
708 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
709 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
710 if (!lstrcmpiW (copycmd, parmY))
711 prompt = FALSE;
712 else if (!lstrcmpiW (copycmd, parmNoY))
713 prompt = TRUE;
717 /* Calculate the destination now - if none supplied, its current dir +
718 filename of first file in list*/
719 if (destination == NULL) {
721 WINE_TRACE("No destination supplied, so need to calculate it\n");
722 strcpyW(destname, dotW);
723 strcatW(destname, slashW);
725 destination = HeapAlloc(GetProcessHeap(),0,sizeof(COPY_FILES));
726 if (destination == NULL) goto exitreturn;
727 destination->concatenate = FALSE; /* Not used for destination */
728 destination->binarycopy = binarymode;
729 destination->next = NULL; /* Not used for destination */
730 destination->name = NULL; /* To be filled in */
731 destisdirectory = TRUE;
733 } else {
734 WCHAR *filenamepart;
735 DWORD attributes;
737 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
739 /* Convert to fully qualified path/filename */
740 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
741 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
743 /* If parameter is a directory, ensure it ends in \ */
744 attributes = GetFileAttributesW(destname);
745 if ((destname[strlenW(destname) - 1] == '\\') ||
746 ((attributes != INVALID_FILE_ATTRIBUTES) &&
747 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
749 destisdirectory = TRUE;
750 if (!(destname[strlenW(destname) - 1] == '\\')) strcatW(destname, slashW);
751 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
755 /* Normally, the destination is the current directory unless we are
756 concatenating, in which case its current directory plus first filename.
757 Note that if the
758 In addition by default it is a binary copy unless concatenating, when
759 the copy defaults to an ascii copy (stop at EOF). We do not know the
760 first source part yet (until we search) so flag as needing filling in. */
762 if (anyconcats) {
763 /* We have found an a+b type syntax, so destination has to be a filename
764 and we need to default to ascii copying. If we have been supplied a
765 directory as the destination, we need to defer calculating the name */
766 if (destisdirectory) appendfirstsource = TRUE;
767 if (destination->binarycopy == -1) destination->binarycopy = 0;
769 } else if (!destisdirectory) {
770 /* We have been asked to copy to a filename. Default to ascii IF the
771 source contains wildcards (true even if only one match) */
772 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
773 anyconcats = TRUE; /* We really are concatenating to a single file */
774 if (destination->binarycopy == -1) {
775 destination->binarycopy = 0;
777 } else {
778 if (destination->binarycopy == -1) {
779 destination->binarycopy = 1;
784 /* Save away the destination name*/
785 HeapFree(GetProcessHeap(), 0, destination->name);
786 destination->name = WCMD_strdupW(destname);
787 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
788 wine_dbgstr_w(destname), appendfirstsource);
790 /* Now we need to walk the set of sources, and process each name we come to.
791 If anyconcats is true, we are writing to one file, otherwise we are using
792 the source name each time.
793 If destination exists, prompt for overwrite the first time (if concatenating
794 we ask each time until yes is answered)
795 The first source file we come across must exist (when wildcards expanded)
796 and if concatenating with overwrite prompts, each source file must exist
797 until a yes is answered. */
799 thiscopy = sourcelist;
800 prevcopy = NULL;
802 while (thiscopy != NULL) {
804 WCHAR srcpath[MAX_PATH];
805 WCHAR *filenamepart;
806 DWORD attributes;
808 /* If it was not explicit, we now know whether we are concatenating or not and
809 hence whether to copy as binary or ascii */
810 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
812 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
813 to where the filename portion begins (used for wildcart expansion. */
814 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
815 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
817 /* If parameter is a directory, ensure it ends in \* */
818 attributes = GetFileAttributesW(srcpath);
819 if (srcpath[strlenW(srcpath) - 1] == '\\') {
821 /* We need to know where the filename part starts, so append * and
822 recalculate the full resulting path */
823 strcatW(thiscopy->name, starW);
824 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
825 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
827 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
828 (attributes != INVALID_FILE_ATTRIBUTES) &&
829 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
831 /* We need to know where the filename part starts, so append \* and
832 recalculate the full resulting path */
833 strcatW(thiscopy->name, slashstarW);
834 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
835 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
838 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
839 wine_dbgstr_w(srcpath), anyconcats);
841 /* Loop through all source files */
842 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
843 hff = FindFirstFileW(srcpath, &fd);
844 if (hff != INVALID_HANDLE_VALUE) {
845 do {
846 WCHAR outname[MAX_PATH];
847 BOOL overwrite;
849 /* Skip . and .., and directories */
850 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
851 WINE_TRACE("Skipping directories\n");
852 } else {
854 /* Build final destination name */
855 strcpyW(outname, destination->name);
856 if (destisdirectory || appendfirstsource) strcatW(outname, fd.cFileName);
858 /* Build source name */
859 strcpyW(filenamepart, fd.cFileName);
861 /* Do we just overwrite */
862 overwrite = !prompt;
863 if (anyconcats && writtenoneconcat) {
864 overwrite = TRUE;
867 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
868 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
869 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
870 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
872 /* Prompt before overwriting */
873 if (!overwrite) {
874 DWORD attributes = GetFileAttributesW(outname);
875 if (attributes != INVALID_FILE_ATTRIBUTES) {
876 WCHAR* question;
877 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
878 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
879 LocalFree(question);
881 else overwrite = TRUE;
884 /* If we needed tyo save away the first filename, do it */
885 if (appendfirstsource && overwrite) {
886 HeapFree(GetProcessHeap(), 0, destination->name);
887 destination->name = WCMD_strdupW(outname);
888 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
889 appendfirstsource = FALSE;
890 destisdirectory = FALSE;
893 /* Do the copy as appropriate */
894 if (overwrite) {
895 if (anyconcats && writtenoneconcat) {
896 if (thiscopy->binarycopy) {
897 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
898 } else {
899 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
901 } else if (!thiscopy->binarycopy) {
902 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
903 } else {
904 status = CopyFileW(srcpath, outname, FALSE);
906 if (!status) {
907 WCMD_print_error ();
908 errorlevel = 1;
909 } else {
910 WINE_TRACE("Copied successfully\n");
911 if (anyconcats) writtenoneconcat = TRUE;
913 /* Append EOF if ascii destination and we are not going to add more onto the end
914 Note: Testing shows windows has an optimization whereas if you have a binary
915 copy of a file to a single destination (ie concatenation) then it does not add
916 the EOF, hence the check on the source copy type below. */
917 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
918 if (!WCMD_AppendEOF(outname)) {
919 WCMD_print_error ();
920 errorlevel = 1;
926 } while (FindNextFileW(hff, &fd) != 0);
927 FindClose (hff);
928 } else {
929 /* Error if the first file was not found */
930 if (!anyconcats || (anyconcats && !writtenoneconcat)) {
931 WCMD_print_error ();
932 errorlevel = 1;
936 /* Step on to the next supplied source */
937 thiscopy = thiscopy -> next;
940 /* Append EOF if ascii destination and we were concatenating */
941 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
942 if (!WCMD_AppendEOF(destination->name)) {
943 WCMD_print_error ();
944 errorlevel = 1;
948 /* Exit out of the routine, freeing any remaining allocated memory */
949 exitreturn:
951 thiscopy = sourcelist;
952 while (thiscopy != NULL) {
953 prevcopy = thiscopy;
954 /* Free up this block*/
955 thiscopy = thiscopy -> next;
956 HeapFree(GetProcessHeap(), 0, prevcopy->name);
957 HeapFree(GetProcessHeap(), 0, prevcopy);
960 /* Free up the destination memory */
961 if (destination) {
962 HeapFree(GetProcessHeap(), 0, destination->name);
963 HeapFree(GetProcessHeap(), 0, destination);
966 return;
969 /****************************************************************************
970 * WCMD_create_dir
972 * Create a directory (and, if needed, any intermediate directories).
974 * Modifies its argument by replacing slashes temporarily with nulls.
977 static BOOL create_full_path(WCHAR* path)
979 WCHAR *p, *start;
981 /* don't mess with drive letter portion of path, if any */
982 start = path;
983 if (path[1] == ':')
984 start = path+2;
986 /* Strip trailing slashes. */
987 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
988 *p = 0;
990 /* Step through path, creating intermediate directories as needed. */
991 /* First component includes drive letter, if any. */
992 p = start;
993 for (;;) {
994 DWORD rv;
995 /* Skip to end of component */
996 while (*p == '\\') p++;
997 while (*p && *p != '\\') p++;
998 if (!*p) {
999 /* path is now the original full path */
1000 return CreateDirectoryW(path, NULL);
1002 /* Truncate path, create intermediate directory, and restore path */
1003 *p = 0;
1004 rv = CreateDirectoryW(path, NULL);
1005 *p = '\\';
1006 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1007 return FALSE;
1009 /* notreached */
1010 return FALSE;
1013 void WCMD_create_dir (WCHAR *args) {
1014 int argno = 0;
1015 WCHAR *argN = args;
1017 if (param1[0] == 0x00) {
1018 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1019 return;
1021 /* Loop through all args */
1022 while (TRUE) {
1023 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1024 if (!argN) break;
1025 if (!create_full_path(thisArg)) {
1026 WCMD_print_error ();
1027 errorlevel = 1;
1032 /* Parse the /A options given by the user on the commandline
1033 * into a bitmask of wanted attributes (*wantSet),
1034 * and a bitmask of unwanted attributes (*wantClear).
1036 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1037 static const WCHAR parmA[] = {'/','A','\0'};
1038 WCHAR *p;
1040 /* both are strictly 'out' parameters */
1041 *wantSet=0;
1042 *wantClear=0;
1044 /* For each /A argument */
1045 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1046 /* Skip /A itself */
1047 p += 2;
1049 /* Skip optional : */
1050 if (*p == ':') p++;
1052 /* For each of the attribute specifier chars to this /A option */
1053 for (; *p != 0 && *p != '/'; p++) {
1054 BOOL negate = FALSE;
1055 DWORD mask = 0;
1057 if (*p == '-') {
1058 negate=TRUE;
1059 p++;
1062 /* Convert the attribute specifier to a bit in one of the masks */
1063 switch (*p) {
1064 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1065 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1066 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1067 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1068 default:
1069 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1071 if (negate)
1072 *wantClear |= mask;
1073 else
1074 *wantSet |= mask;
1079 /* If filename part of parameter is * or *.*,
1080 * and neither /Q nor /P options were given,
1081 * prompt the user whether to proceed.
1082 * Returns FALSE if user says no, TRUE otherwise.
1083 * *pPrompted is set to TRUE if the user is prompted.
1084 * (If /P supplied, del will prompt for individual files later.)
1086 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1087 static const WCHAR parmP[] = {'/','P','\0'};
1088 static const WCHAR parmQ[] = {'/','Q','\0'};
1090 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1091 static const WCHAR anyExt[]= {'.','*','\0'};
1092 WCHAR drive[10];
1093 WCHAR dir[MAX_PATH];
1094 WCHAR fname[MAX_PATH];
1095 WCHAR ext[MAX_PATH];
1096 WCHAR fpath[MAX_PATH];
1098 /* Convert path into actual directory spec */
1099 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1100 WCMD_splitpath(fpath, drive, dir, fname, ext);
1102 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1103 if ((strcmpW(fname, starW) == 0) &&
1104 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1106 WCHAR question[MAXSTRING];
1107 static const WCHAR fmt[] = {'%','s',' ','\0'};
1109 /* Caller uses this to suppress "file not found" warning later */
1110 *pPrompted = TRUE;
1112 /* Ask for confirmation */
1113 wsprintfW(question, fmt, fpath);
1114 return WCMD_ask_confirm(question, TRUE, NULL);
1117 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1118 return TRUE;
1121 /* Helper function for WCMD_delete().
1122 * Deletes a single file, directory, or wildcard.
1123 * If /S was given, does it recursively.
1124 * Returns TRUE if a file was deleted.
1126 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1128 static const WCHAR parmP[] = {'/','P','\0'};
1129 static const WCHAR parmS[] = {'/','S','\0'};
1130 static const WCHAR parmF[] = {'/','F','\0'};
1131 DWORD wanted_attrs;
1132 DWORD unwanted_attrs;
1133 BOOL found = FALSE;
1134 WCHAR argCopy[MAX_PATH];
1135 WIN32_FIND_DATAW fd;
1136 HANDLE hff;
1137 WCHAR fpath[MAX_PATH];
1138 WCHAR *p;
1139 BOOL handleParm = TRUE;
1141 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1143 strcpyW(argCopy, thisArg);
1144 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1145 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1147 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1148 /* Skip this arg if user declines to delete *.* */
1149 return FALSE;
1152 /* First, try to delete in the current directory */
1153 hff = FindFirstFileW(argCopy, &fd);
1154 if (hff == INVALID_HANDLE_VALUE) {
1155 handleParm = FALSE;
1156 } else {
1157 found = TRUE;
1160 /* Support del <dirname> by just deleting all files dirname\* */
1161 if (handleParm
1162 && (strchrW(argCopy,'*') == NULL)
1163 && (strchrW(argCopy,'?') == NULL)
1164 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1166 WCHAR modifiedParm[MAX_PATH];
1167 static const WCHAR slashStar[] = {'\\','*','\0'};
1169 strcpyW(modifiedParm, argCopy);
1170 strcatW(modifiedParm, slashStar);
1171 FindClose(hff);
1172 found = TRUE;
1173 WCMD_delete_one(modifiedParm);
1175 } else if (handleParm) {
1177 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1178 strcpyW (fpath, argCopy);
1179 do {
1180 p = strrchrW (fpath, '\\');
1181 if (p != NULL) {
1182 *++p = '\0';
1183 strcatW (fpath, fd.cFileName);
1185 else strcpyW (fpath, fd.cFileName);
1186 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1187 BOOL ok;
1189 /* Handle attribute matching (/A) */
1190 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1191 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1193 /* /P means prompt for each file */
1194 if (ok && strstrW (quals, parmP) != NULL) {
1195 WCHAR* question;
1197 /* Ask for confirmation */
1198 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1199 ok = WCMD_ask_confirm(question, FALSE, NULL);
1200 LocalFree(question);
1203 /* Only proceed if ok to */
1204 if (ok) {
1206 /* If file is read only, and /A:r or /F supplied, delete it */
1207 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1208 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1209 strstrW (quals, parmF) != NULL)) {
1210 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1213 /* Now do the delete */
1214 if (!DeleteFileW(fpath)) WCMD_print_error ();
1218 } while (FindNextFileW(hff, &fd) != 0);
1219 FindClose (hff);
1222 /* Now recurse into all subdirectories handling the parameter in the same way */
1223 if (strstrW (quals, parmS) != NULL) {
1225 WCHAR thisDir[MAX_PATH];
1226 int cPos;
1228 WCHAR drive[10];
1229 WCHAR dir[MAX_PATH];
1230 WCHAR fname[MAX_PATH];
1231 WCHAR ext[MAX_PATH];
1233 /* Convert path into actual directory spec */
1234 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1235 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1237 strcpyW(thisDir, drive);
1238 strcatW(thisDir, dir);
1239 cPos = strlenW(thisDir);
1241 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1243 /* Append '*' to the directory */
1244 thisDir[cPos] = '*';
1245 thisDir[cPos+1] = 0x00;
1247 hff = FindFirstFileW(thisDir, &fd);
1249 /* Remove residual '*' */
1250 thisDir[cPos] = 0x00;
1252 if (hff != INVALID_HANDLE_VALUE) {
1253 DIRECTORY_STACK *allDirs = NULL;
1254 DIRECTORY_STACK *lastEntry = NULL;
1256 do {
1257 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1258 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1259 (strcmpW(fd.cFileName, dotW) != 0)) {
1261 DIRECTORY_STACK *nextDir;
1262 WCHAR subParm[MAX_PATH];
1264 /* Work out search parameter in sub dir */
1265 strcpyW (subParm, thisDir);
1266 strcatW (subParm, fd.cFileName);
1267 strcatW (subParm, slashW);
1268 strcatW (subParm, fname);
1269 strcatW (subParm, ext);
1270 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1272 /* Allocate memory, add to list */
1273 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
1274 if (allDirs == NULL) allDirs = nextDir;
1275 if (lastEntry != NULL) lastEntry->next = nextDir;
1276 lastEntry = nextDir;
1277 nextDir->next = NULL;
1278 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
1279 (strlenW(subParm)+1) * sizeof(WCHAR));
1280 strcpyW(nextDir->dirName, subParm);
1282 } while (FindNextFileW(hff, &fd) != 0);
1283 FindClose (hff);
1285 /* Go through each subdir doing the delete */
1286 while (allDirs != NULL) {
1287 DIRECTORY_STACK *tempDir;
1289 tempDir = allDirs->next;
1290 found |= WCMD_delete_one (allDirs->dirName);
1292 HeapFree(GetProcessHeap(),0,allDirs->dirName);
1293 HeapFree(GetProcessHeap(),0,allDirs);
1294 allDirs = tempDir;
1299 return found;
1302 /****************************************************************************
1303 * WCMD_delete
1305 * Delete a file or wildcarded set.
1307 * Note on /A:
1308 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1309 * - Each set is a pattern, eg /ahr /as-r means
1310 * readonly+hidden OR nonreadonly system files
1311 * - The '-' applies to a single field, ie /a:-hr means read only
1312 * non-hidden files
1315 BOOL WCMD_delete (WCHAR *args) {
1316 int argno;
1317 WCHAR *argN;
1318 BOOL argsProcessed = FALSE;
1319 BOOL foundAny = FALSE;
1321 errorlevel = 0;
1323 for (argno=0; ; argno++) {
1324 BOOL found;
1325 WCHAR *thisArg;
1327 argN = NULL;
1328 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1329 if (!argN)
1330 break; /* no more parameters */
1331 if (argN[0] == '/')
1332 continue; /* skip options */
1334 argsProcessed = TRUE;
1335 found = WCMD_delete_one(thisArg);
1336 if (!found) {
1337 errorlevel = 1;
1338 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1340 foundAny |= found;
1343 /* Handle no valid args */
1344 if (!argsProcessed)
1345 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1347 return foundAny;
1351 * WCMD_strtrim
1353 * Returns a trimmed version of s with all leading and trailing whitespace removed
1354 * Pre: s non NULL
1357 static WCHAR *WCMD_strtrim(const WCHAR *s)
1359 DWORD len = strlenW(s);
1360 const WCHAR *start = s;
1361 WCHAR* result;
1363 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
1364 return NULL;
1366 while (isspaceW(*start)) start++;
1367 if (*start) {
1368 const WCHAR *end = s + len - 1;
1369 while (end > start && isspaceW(*end)) end--;
1370 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1371 result[end - start + 1] = '\0';
1372 } else {
1373 result[0] = '\0';
1376 return result;
1379 /****************************************************************************
1380 * WCMD_echo
1382 * Echo input to the screen (or not). We don't try to emulate the bugs
1383 * in DOS (try typing "ECHO ON AGAIN" for an example).
1386 void WCMD_echo (const WCHAR *args)
1388 int count;
1389 const WCHAR *origcommand = args;
1390 WCHAR *trimmed;
1392 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1393 || args[0]==':' || args[0]==';')
1394 args++;
1396 trimmed = WCMD_strtrim(args);
1397 if (!trimmed) return;
1399 count = strlenW(trimmed);
1400 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1401 && origcommand[0]!=';') {
1402 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1403 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1404 return;
1407 if (lstrcmpiW(trimmed, onW) == 0)
1408 echo_mode = TRUE;
1409 else if (lstrcmpiW(trimmed, offW) == 0)
1410 echo_mode = FALSE;
1411 else {
1412 WCMD_output_asis (args);
1413 WCMD_output_asis (newlineW);
1415 HeapFree(GetProcessHeap(), 0, trimmed);
1418 /*****************************************************************************
1419 * WCMD_part_execute
1421 * Execute a command, and any && or bracketed follow on to the command. The
1422 * first command to be executed may not be at the front of the
1423 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1425 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1426 const WCHAR *variable, const WCHAR *value,
1427 BOOL isIF, BOOL executecmds)
1429 CMD_LIST *curPosition = *cmdList;
1430 int myDepth = (*cmdList)->bracketDepth;
1432 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1433 cmdList, wine_dbgstr_w(firstcmd),
1434 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1435 executecmds);
1437 /* Skip leading whitespace between condition and the command */
1438 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1440 /* Process the first command, if there is one */
1441 if (executecmds && firstcmd && *firstcmd) {
1442 WCHAR *command = WCMD_strdupW(firstcmd);
1443 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList, FALSE);
1444 HeapFree(GetProcessHeap(), 0, command);
1448 /* If it didn't move the position, step to next command */
1449 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1451 /* Process any other parts of the command */
1452 if (*cmdList) {
1453 BOOL processThese = executecmds;
1455 while (*cmdList) {
1456 static const WCHAR ifElse[] = {'e','l','s','e'};
1458 /* execute all appropriate commands */
1459 curPosition = *cmdList;
1461 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1462 *cmdList,
1463 (*cmdList)->prevDelim,
1464 (*cmdList)->bracketDepth, myDepth);
1466 /* Execute any statements appended to the line */
1467 /* FIXME: Only if previous call worked for && or failed for || */
1468 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1469 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1470 if (processThese && (*cmdList)->command) {
1471 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1472 value, cmdList, FALSE);
1474 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1476 /* Execute any appended to the statement with (...) */
1477 } else if ((*cmdList)->bracketDepth > myDepth) {
1478 if (processThese) {
1479 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value, FALSE);
1480 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1482 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1484 /* End of the command - does 'ELSE ' follow as the next command? */
1485 } else {
1486 if (isIF
1487 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1488 (*cmdList)->command)) {
1490 /* Swap between if and else processing */
1491 processThese = !processThese;
1493 /* Process the ELSE part */
1494 if (processThese) {
1495 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1496 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1498 /* Skip leading whitespace between condition and the command */
1499 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1500 if (*cmd) {
1501 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList, FALSE);
1504 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1505 } else {
1506 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1507 break;
1512 return;
1515 /*****************************************************************************
1516 * WCMD_parse_forf_options
1518 * Parses the for /f 'options', extracting the values and validating the
1519 * keywords. Note all keywords are optional.
1520 * Parameters:
1521 * options [I] The unparsed parameter string
1522 * eol [O] Set to the comment character (eol=x)
1523 * skip [O] Set to the number of lines to skip (skip=xx)
1524 * delims [O] Set to the token delimiters (delims=)
1525 * tokens [O] Set to the requested tokens, as provided (tokens=)
1526 * usebackq [O] Set to TRUE if usebackq found
1528 * Returns TRUE on success, FALSE on syntax error
1531 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1532 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1535 WCHAR *pos = options;
1536 int len = strlenW(pos);
1537 static const WCHAR eolW[] = {'e','o','l','='};
1538 static const WCHAR skipW[] = {'s','k','i','p','='};
1539 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1540 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1541 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1542 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1543 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1545 /* Initialize to defaults */
1546 strcpyW(delims, forf_defaultdelims);
1547 strcpyW(tokens, forf_defaulttokens);
1548 *eol = 0;
1549 *skip = 0;
1550 *usebackq = FALSE;
1552 /* Strip (optional) leading and trailing quotes */
1553 if ((*pos == '"') && (pos[len-1] == '"')) {
1554 pos[len-1] = 0;
1555 pos++;
1558 /* Process each keyword */
1559 while (pos && *pos) {
1560 if (*pos == ' ' || *pos == '\t') {
1561 pos++;
1563 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1564 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1565 pos, sizeof(eolW)/sizeof(WCHAR),
1566 eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1567 *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1568 pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1569 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1571 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1572 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1573 pos, sizeof(skipW)/sizeof(WCHAR),
1574 skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1575 WCHAR *nextchar = NULL;
1576 pos = pos + sizeof(skipW)/sizeof(WCHAR);
1577 *skip = strtoulW(pos, &nextchar, 0);
1578 WINE_TRACE("Found skip as %d lines\n", *skip);
1579 pos = nextchar;
1581 /* Save if usebackq semantics are in effect */
1582 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1583 pos, sizeof(usebackqW)/sizeof(WCHAR),
1584 usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1585 *usebackq = TRUE;
1586 pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1587 WINE_TRACE("Found usebackq\n");
1589 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1590 if you finish the optionsroot string with delims= otherwise the space is
1591 just a token delimiter! */
1592 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1593 pos, sizeof(delimsW)/sizeof(WCHAR),
1594 delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1595 int i=0;
1597 pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1598 while (*pos && *pos != ' ') {
1599 delims[i++] = *pos;
1600 pos++;
1602 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1603 delims[i++] = 0; /* Null terminate the delims */
1604 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1606 /* Save the tokens being requested */
1607 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1608 pos, sizeof(tokensW)/sizeof(WCHAR),
1609 tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1610 int i=0;
1612 pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1613 while (*pos && *pos != ' ') {
1614 tokens[i++] = *pos;
1615 pos++;
1617 tokens[i++] = 0; /* Null terminate the tokens */
1618 WINE_FIXME("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1620 } else {
1621 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1622 return FALSE;
1625 return TRUE;
1628 /*****************************************************************************
1629 * WCMD_add_dirstowalk
1631 * When recursing through directories (for /r), we need to add to the list of
1632 * directories still to walk, any subdirectories of the one we are processing.
1634 * Parameters
1635 * options [I] The remaining list of directories still to process
1637 * Note this routine inserts the subdirectories found between the entry being
1638 * processed, and any other directory still to be processed, mimicing what
1639 * Windows does
1641 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1642 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1643 WCHAR fullitem[MAX_PATH];
1644 WIN32_FIND_DATAW fd;
1645 HANDLE hff;
1647 /* Build a generic search and add all directories on the list of directories
1648 still to walk */
1649 strcpyW(fullitem, dirsToWalk->dirName);
1650 strcatW(fullitem, slashstarW);
1651 hff = FindFirstFileW(fullitem, &fd);
1652 if (hff != INVALID_HANDLE_VALUE) {
1653 do {
1654 WINE_TRACE("Looking for subdirectories\n");
1655 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1656 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1657 (strcmpW(fd.cFileName, dotW) != 0))
1659 /* Allocate memory, add to list */
1660 DIRECTORY_STACK *toWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1661 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1662 toWalk->next = remainingDirs->next;
1663 remainingDirs->next = toWalk;
1664 remainingDirs = toWalk;
1665 toWalk->dirName = HeapAlloc(GetProcessHeap(), 0,
1666 sizeof(WCHAR) *
1667 (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1668 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1669 strcatW(toWalk->dirName, slashW);
1670 strcatW(toWalk->dirName, fd.cFileName);
1671 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1672 toWalk, toWalk->next);
1674 } while (FindNextFileW(hff, &fd) != 0);
1675 WINE_TRACE("Finished adding all subdirectories\n");
1676 FindClose (hff);
1680 /**************************************************************************
1681 * WCMD_parse_line
1683 * When parsing file or string contents (for /f), once the string to parse
1684 * has been identified, handle the various options and call the do part
1685 * if appropriate.
1687 * Parameters:
1688 * cmdStart [I] - Identifies the list of commands making up the
1689 * for loop body (especially if brackets in use)
1690 * firstCmd [I] - The textual start of the command after the DO
1691 * which is within the first item of cmdStart
1692 * cmdEnd [O] - Identifies where to continue after the DO
1693 * variable [I] - The variable identified on the for line
1694 * buffer [I] - The string to parse
1695 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1696 * forf_skip [I/O] - How many lines to skip first
1697 * forf_eol [I] - The 'end of line' (comment) character
1698 * forf_delims [I] - The delimiters to use when breaking the string apart
1700 static void WCMD_parse_line(CMD_LIST *cmdStart,
1701 const WCHAR *firstCmd,
1702 CMD_LIST **cmdEnd,
1703 const WCHAR *variable,
1704 WCHAR *buffer,
1705 BOOL *doExecuted,
1706 int *forf_skip,
1707 WCHAR forf_eol,
1708 WCHAR *forf_delims) {
1710 WCHAR *parm, *where;
1712 /* Skip lines if requested */
1713 if (*forf_skip) {
1714 (*forf_skip)--;
1715 return;
1718 /* Extract the parameter */
1719 parm = WCMD_parameter_with_delims(buffer, 0, &where, FALSE, FALSE, forf_delims);
1720 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1721 wine_dbgstr_w(buffer));
1723 if (where && where[0] != forf_eol) {
1724 CMD_LIST *thisCmdStart = cmdStart;
1725 *doExecuted = TRUE;
1726 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1727 *cmdEnd = thisCmdStart;
1732 /**************************************************************************
1733 * WCMD_forf_getinputhandle
1735 * Return a file handle which can be used for reading the input lines,
1736 * either to a specific file (which may be quote delimited as we have to
1737 * read the parameters in raw mode) or to a command which we need to
1738 * execute. The command being executed runs in its own shell and stores
1739 * its data in a temporary file.
1741 * Parameters:
1742 * usebackq [I] - Indicates whether usebackq is in effect or not
1743 * itemStr [I] - The item to be handled, either a filename or
1744 * whole command string to execute
1745 * iscmd [I] - Identifies whether this is a command or not
1747 * Returns a file handle which can be used to read the input lines from.
1749 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
1750 WCHAR temp_str[MAX_PATH];
1751 WCHAR temp_file[MAX_PATH];
1752 WCHAR temp_cmd[MAXSTRING];
1753 HANDLE hinput = INVALID_HANDLE_VALUE;
1754 static const WCHAR redirOutW[] = {'>','%','s','\0'};
1755 static const WCHAR cmdW[] = {'C','M','D','\0'};
1756 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
1757 '/','C',' ','"','%','s','"','\0'};
1759 /* Remove leading and trailing character */
1760 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
1761 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
1762 (!iscmd && (itemstr[0] == '"' && usebackq)))
1764 itemstr[strlenW(itemstr)-1] = 0x00;
1765 itemstr++;
1768 if (iscmd) {
1769 /* Get temp filename */
1770 GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
1771 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
1773 /* Redirect output to the temporary file */
1774 wsprintfW(temp_str, redirOutW, temp_file);
1775 wsprintfW(temp_cmd, cmdslashcW, itemstr);
1776 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
1777 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
1778 WCMD_execute (temp_cmd, temp_str, NULL, NULL, NULL, FALSE);
1780 /* Open the file, read line by line and process */
1781 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1782 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
1784 } else {
1785 /* Open the file, read line by line and process */
1786 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
1787 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
1788 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1790 return hinput;
1793 /**************************************************************************
1794 * WCMD_for
1796 * Batch file loop processing.
1798 * On entry: cmdList contains the syntax up to the set
1799 * next cmdList and all in that bracket contain the set data
1800 * next cmdlist contains the DO cmd
1801 * following that is either brackets or && entries (as per if)
1805 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1807 WIN32_FIND_DATAW fd;
1808 HANDLE hff;
1809 int i;
1810 static const WCHAR inW[] = {'i','n'};
1811 static const WCHAR doW[] = {'d','o'};
1812 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1813 WCHAR variable[4];
1814 WCHAR *firstCmd;
1815 int thisDepth;
1816 WCHAR optionsRoot[MAX_PATH];
1817 DIRECTORY_STACK *dirsToWalk = NULL;
1818 BOOL expandDirs = FALSE;
1819 BOOL useNumbers = FALSE;
1820 BOOL doFileset = FALSE;
1821 BOOL doRecurse = FALSE;
1822 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
1823 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1824 int itemNum;
1825 CMD_LIST *thisCmdStart;
1826 int parameterNo = 0;
1827 WCHAR forf_eol = 0;
1828 int forf_skip = 0;
1829 WCHAR forf_delims[256];
1830 WCHAR forf_tokens[MAXSTRING];
1831 BOOL forf_usebackq = FALSE;
1833 /* Handle optional qualifiers (multiple are allowed) */
1834 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
1836 optionsRoot[0] = 0;
1837 while (thisArg && *thisArg == '/') {
1838 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
1839 thisArg++;
1840 switch (toupperW(*thisArg)) {
1841 case 'D': expandDirs = TRUE; break;
1842 case 'L': useNumbers = TRUE; break;
1844 /* Recursive is special case - /R can have an optional path following it */
1845 /* filenamesets are another special case - /F can have an optional options following it */
1846 case 'R':
1847 case 'F':
1849 /* When recursing directories, use current directory as the starting point unless
1850 subsequently overridden */
1851 doRecurse = (toupperW(*thisArg) == 'R');
1852 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
1854 doFileset = (toupperW(*thisArg) == 'F');
1856 /* Retrieve next parameter to see if is root/options (raw form required
1857 with for /f, or unquoted in for /r) */
1858 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
1860 /* Next parm is either qualifier, path/options or variable -
1861 only care about it if it is the path/options */
1862 if (thisArg && *thisArg != '/' && *thisArg != '%') {
1863 parameterNo++;
1864 strcpyW(optionsRoot, thisArg);
1866 break;
1868 default:
1869 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
1872 /* Step to next token */
1873 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
1876 /* Ensure line continues with variable */
1877 if (!*thisArg || *thisArg != '%') {
1878 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1879 return;
1882 /* With for /f parse the options if provided */
1883 if (doFileset) {
1884 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
1885 forf_delims, forf_tokens, &forf_usebackq))
1887 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1888 return;
1891 /* Set up the list of directories to recurse if we are going to */
1892 } else if (doRecurse) {
1893 /* Allocate memory, add to list */
1894 dirsToWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1895 dirsToWalk->next = NULL;
1896 dirsToWalk->dirName = HeapAlloc(GetProcessHeap(),0,
1897 (strlenW(optionsRoot) + 1) * sizeof(WCHAR));
1898 strcpyW(dirsToWalk->dirName, optionsRoot);
1899 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
1902 /* Variable should follow */
1903 strcpyW(variable, thisArg);
1904 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1906 /* Ensure line continues with IN */
1907 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
1908 if (!thisArg
1909 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1910 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
1911 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
1912 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1913 return;
1916 /* Save away where the set of data starts and the variable */
1917 thisDepth = (*cmdList)->bracketDepth;
1918 *cmdList = (*cmdList)->nextcommand;
1919 setStart = (*cmdList);
1921 /* Skip until the close bracket */
1922 WINE_TRACE("Searching %p as the set\n", *cmdList);
1923 while (*cmdList &&
1924 (*cmdList)->command != NULL &&
1925 (*cmdList)->bracketDepth > thisDepth) {
1926 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1927 *cmdList = (*cmdList)->nextcommand;
1930 /* Skip the close bracket, if there is one */
1931 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1933 /* Syntax error if missing close bracket, or nothing following it
1934 and once we have the complete set, we expect a DO */
1935 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1936 if ((*cmdList == NULL)
1937 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1939 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1940 return;
1943 cmdEnd = *cmdList;
1945 /* Loop repeatedly per-directory we are potentially walking, when in for /r
1946 mode, or once for the rest of the time. */
1947 do {
1949 /* Save away the starting position for the commands (and offset for the
1950 first one) */
1951 cmdStart = *cmdList;
1952 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1953 itemNum = 0;
1955 /* If we are recursing directories (ie /R), add all sub directories now, then
1956 prefix the root when searching for the item */
1957 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
1959 thisSet = setStart;
1960 /* Loop through all set entries */
1961 while (thisSet &&
1962 thisSet->command != NULL &&
1963 thisSet->bracketDepth >= thisDepth) {
1965 /* Loop through all entries on the same line */
1966 WCHAR *item;
1967 WCHAR *itemStart;
1968 WCHAR buffer[MAXSTRING];
1970 WINE_TRACE("Processing for set %p\n", thisSet);
1971 i = 0;
1972 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
1975 * If the parameter within the set has a wildcard then search for matching files
1976 * otherwise do a literal substitution.
1978 static const WCHAR wildcards[] = {'*','?','\0'};
1979 thisCmdStart = cmdStart;
1981 itemNum++;
1982 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1984 if (!useNumbers && !doFileset) {
1985 WCHAR fullitem[MAX_PATH];
1987 /* Now build the item to use / search for in the specified directory,
1988 as it is fully qualified in the /R case */
1989 if (dirsToWalk) {
1990 strcpyW(fullitem, dirsToWalk->dirName);
1991 strcatW(fullitem, slashW);
1992 strcatW(fullitem, item);
1993 } else {
1994 strcpyW(fullitem, item);
1997 if (strpbrkW (fullitem, wildcards)) {
1999 hff = FindFirstFileW(fullitem, &fd);
2000 if (hff != INVALID_HANDLE_VALUE) {
2001 do {
2002 BOOL isDirectory = FALSE;
2004 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2006 /* Handle as files or dirs appropriately, but ignore . and .. */
2007 if (isDirectory == expandDirs &&
2008 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2009 (strcmpW(fd.cFileName, dotW) != 0))
2011 thisCmdStart = cmdStart;
2012 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2014 if (doRecurse) {
2015 strcpyW(fullitem, dirsToWalk->dirName);
2016 strcatW(fullitem, slashW);
2017 strcatW(fullitem, fd.cFileName);
2018 } else {
2019 strcpyW(fullitem, fd.cFileName);
2021 doExecuted = TRUE;
2022 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
2023 fullitem, FALSE, TRUE);
2024 cmdEnd = thisCmdStart;
2026 } while (FindNextFileW(hff, &fd) != 0);
2027 FindClose (hff);
2029 } else {
2030 doExecuted = TRUE;
2031 WCMD_part_execute(&thisCmdStart, firstCmd, variable, fullitem, FALSE, TRUE);
2032 cmdEnd = thisCmdStart;
2035 } else if (useNumbers) {
2036 /* Convert the first 3 numbers to signed longs and save */
2037 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2038 /* else ignore them! */
2040 /* Filesets - either a list of files, or a command to run and parse the output */
2041 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2042 (forf_usebackq && *itemStart != '\''))) {
2044 HANDLE input;
2045 WCHAR *itemparm;
2047 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2048 wine_dbgstr_w(item));
2050 /* If backquote or single quote, we need to launch that command
2051 and parse the results - use a temporary file */
2052 if ((forf_usebackq && *itemStart == '`') ||
2053 (!forf_usebackq && *itemStart == '\'')) {
2055 /* Use itemstart because the command is the whole set, not just the first token */
2056 itemparm = itemStart;
2057 } else {
2059 /* Use item because the file to process is just the first item in the set */
2060 itemparm = item;
2062 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2064 /* Process the input file */
2065 if (input == INVALID_HANDLE_VALUE) {
2066 WCMD_print_error ();
2067 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2068 errorlevel = 1;
2069 return; /* FOR loop aborts at first failure here */
2071 } else {
2073 /* Read line by line until end of file */
2074 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2075 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable, buffer, &doExecuted,
2076 &forf_skip, forf_eol, forf_delims);
2077 buffer[0] = 0;
2079 CloseHandle (input);
2082 /* When we have processed the item as a whole command, abort future set processing */
2083 if (itemparm==itemStart) {
2084 thisSet = NULL;
2085 break;
2088 /* Filesets - A string literal */
2089 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2090 (forf_usebackq && *itemStart == '\''))) {
2092 /* Remove leading and trailing character, ready to parse with delims= delimiters
2093 Note that the last quote is removed from the set and the string terminates
2094 there to mimic windows */
2095 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2096 if (strend) {
2097 *strend = 0x00;
2098 itemStart++;
2101 /* Copy the item away from the global buffer used by WCMD_parameter */
2102 strcpyW(buffer, itemStart);
2103 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable, buffer, &doExecuted,
2104 &forf_skip, forf_eol, forf_delims);
2106 /* Only one string can be supplied in the whole set, abort future set processing */
2107 thisSet = NULL;
2108 break;
2111 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2112 i++;
2115 /* Move onto the next set line */
2116 if (thisSet) thisSet = thisSet->nextcommand;
2119 /* If /L is provided, now run the for loop */
2120 if (useNumbers) {
2121 WCHAR thisNum[20];
2122 static const WCHAR fmt[] = {'%','d','\0'};
2124 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2125 numbers[0], numbers[2], numbers[1]);
2126 for (i=numbers[0];
2127 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2128 i=i + numbers[1]) {
2130 sprintfW(thisNum, fmt, i);
2131 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2133 thisCmdStart = cmdStart;
2134 doExecuted = TRUE;
2135 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
2137 cmdEnd = thisCmdStart;
2140 /* If we are walking directories, move on to any which remain */
2141 if (dirsToWalk != NULL) {
2142 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2143 HeapFree(GetProcessHeap(), 0, dirsToWalk->dirName);
2144 HeapFree(GetProcessHeap(), 0, dirsToWalk);
2145 dirsToWalk = nextDir;
2146 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2147 wine_dbgstr_w(dirsToWalk->dirName));
2148 else WINE_TRACE("Finished all directories.\n");
2151 } while (dirsToWalk != NULL);
2153 /* Now skip over the do part if we did not perform the for loop so far.
2154 We store in cmdEnd the next command after the do block, but we only
2155 know this if something was run. If it has not been, we need to calculate
2156 it. */
2157 if (!doExecuted) {
2158 thisCmdStart = cmdStart;
2159 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2160 WCMD_part_execute(&thisCmdStart, firstCmd, NULL, NULL, FALSE, FALSE);
2161 cmdEnd = thisCmdStart;
2164 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2165 all processing, OR it should be pointing to the end of && processing OR
2166 it should be pointing at the NULL end of bracket for the DO. The return
2167 value needs to be the NEXT command to execute, which it either is, or
2168 we need to step over the closing bracket */
2169 *cmdList = cmdEnd;
2170 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2173 /**************************************************************************
2174 * WCMD_give_help
2176 * Simple on-line help. Help text is stored in the resource file.
2179 void WCMD_give_help (const WCHAR *args)
2181 size_t i;
2183 args = WCMD_skip_leading_spaces((WCHAR*) args);
2184 if (strlenW(args) == 0) {
2185 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2187 else {
2188 /* Display help message for builtin commands */
2189 for (i=0; i<=WCMD_EXIT; i++) {
2190 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2191 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2192 WCMD_output_asis (WCMD_LoadMessage(i));
2193 return;
2196 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2197 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2198 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2199 args, -1, externals[i], -1) == CSTR_EQUAL) {
2200 WCHAR cmd[128];
2201 static const WCHAR helpW[] = {' ', '/','?','\0'};
2202 strcpyW(cmd, args);
2203 strcatW(cmd, helpW);
2204 WCMD_run_program(cmd, FALSE);
2205 return;
2208 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2210 return;
2213 /****************************************************************************
2214 * WCMD_go_to
2216 * Batch file jump instruction. Not the most efficient algorithm ;-)
2217 * Prints error message if the specified label cannot be found - the file pointer is
2218 * then at EOF, effectively stopping the batch file.
2219 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2222 void WCMD_goto (CMD_LIST **cmdList) {
2224 WCHAR string[MAX_PATH];
2225 WCHAR current[MAX_PATH];
2227 /* Do not process any more parts of a processed multipart or multilines command */
2228 if (cmdList) *cmdList = NULL;
2230 if (context != NULL) {
2231 WCHAR *paramStart = param1, *str;
2232 static const WCHAR eofW[] = {':','e','o','f','\0'};
2234 if (param1[0] == 0x00) {
2235 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2236 return;
2239 /* Handle special :EOF label */
2240 if (lstrcmpiW (eofW, param1) == 0) {
2241 context -> skip_rest = TRUE;
2242 return;
2245 /* Support goto :label as well as goto label */
2246 if (*paramStart == ':') paramStart++;
2248 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2249 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2250 str = string;
2251 while (isspaceW (*str)) str++;
2252 if (*str == ':') {
2253 DWORD index = 0;
2254 str++;
2255 while (((current[index] = str[index])) && (!isspaceW (current[index])))
2256 index++;
2258 /* ignore space at the end */
2259 current[index] = 0;
2260 if (lstrcmpiW (current, paramStart) == 0) return;
2263 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2265 return;
2268 /*****************************************************************************
2269 * WCMD_pushd
2271 * Push a directory onto the stack
2274 void WCMD_pushd (const WCHAR *args)
2276 struct env_stack *curdir;
2277 WCHAR *thisdir;
2278 static const WCHAR parmD[] = {'/','D','\0'};
2280 if (strchrW(args, '/') != NULL) {
2281 SetLastError(ERROR_INVALID_PARAMETER);
2282 WCMD_print_error();
2283 return;
2286 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2287 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2288 if( !curdir || !thisdir ) {
2289 LocalFree(curdir);
2290 LocalFree(thisdir);
2291 WINE_ERR ("out of memory\n");
2292 return;
2295 /* Change directory using CD code with /D parameter */
2296 strcpyW(quals, parmD);
2297 GetCurrentDirectoryW (1024, thisdir);
2298 errorlevel = 0;
2299 WCMD_setshow_default(args);
2300 if (errorlevel) {
2301 LocalFree(curdir);
2302 LocalFree(thisdir);
2303 return;
2304 } else {
2305 curdir -> next = pushd_directories;
2306 curdir -> strings = thisdir;
2307 if (pushd_directories == NULL) {
2308 curdir -> u.stackdepth = 1;
2309 } else {
2310 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2312 pushd_directories = curdir;
2317 /*****************************************************************************
2318 * WCMD_popd
2320 * Pop a directory from the stack
2323 void WCMD_popd (void) {
2324 struct env_stack *temp = pushd_directories;
2326 if (!pushd_directories)
2327 return;
2329 /* pop the old environment from the stack, and make it the current dir */
2330 pushd_directories = temp->next;
2331 SetCurrentDirectoryW(temp->strings);
2332 LocalFree (temp->strings);
2333 LocalFree (temp);
2336 /*******************************************************************
2337 * evaluate_if_comparison
2339 * Evaluates an "if" comparison operation
2341 * PARAMS
2342 * leftOperand [I] left operand, non NULL
2343 * operator [I] "if" binary comparison operator, non NULL
2344 * rightOperand [I] right operand, non NULL
2345 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2347 * RETURNS
2348 * Success: 1 if operator applied to the operands evaluates to TRUE
2349 * 0 if operator applied to the operands evaluates to FALSE
2350 * Failure: -1 if operator is not recognized
2352 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2353 const WCHAR *rightOperand, int caseInsensitive)
2355 WCHAR *endptr_leftOp, *endptr_rightOp;
2356 long int leftOperand_int, rightOperand_int;
2357 BOOL int_operands;
2358 static const WCHAR lssW[] = {'l','s','s','\0'};
2359 static const WCHAR leqW[] = {'l','e','q','\0'};
2360 static const WCHAR equW[] = {'e','q','u','\0'};
2361 static const WCHAR neqW[] = {'n','e','q','\0'};
2362 static const WCHAR geqW[] = {'g','e','q','\0'};
2363 static const WCHAR gtrW[] = {'g','t','r','\0'};
2365 /* == is a special case, as it always compares strings */
2366 if (!lstrcmpiW(operator, eqeqW))
2367 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2368 : lstrcmpW (leftOperand, rightOperand) == 0;
2370 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2371 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2372 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2373 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2375 /* Perform actual (integer or string) comparison */
2376 if (!lstrcmpiW(operator, lssW)) {
2377 if (int_operands)
2378 return leftOperand_int < rightOperand_int;
2379 else
2380 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2381 : lstrcmpW (leftOperand, rightOperand) < 0;
2384 if (!lstrcmpiW(operator, leqW)) {
2385 if (int_operands)
2386 return leftOperand_int <= rightOperand_int;
2387 else
2388 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2389 : lstrcmpW (leftOperand, rightOperand) <= 0;
2392 if (!lstrcmpiW(operator, equW)) {
2393 if (int_operands)
2394 return leftOperand_int == rightOperand_int;
2395 else
2396 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2397 : lstrcmpW (leftOperand, rightOperand) == 0;
2400 if (!lstrcmpiW(operator, neqW)) {
2401 if (int_operands)
2402 return leftOperand_int != rightOperand_int;
2403 else
2404 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2405 : lstrcmpW (leftOperand, rightOperand) != 0;
2408 if (!lstrcmpiW(operator, geqW)) {
2409 if (int_operands)
2410 return leftOperand_int >= rightOperand_int;
2411 else
2412 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2413 : lstrcmpW (leftOperand, rightOperand) >= 0;
2416 if (!lstrcmpiW(operator, gtrW)) {
2417 if (int_operands)
2418 return leftOperand_int > rightOperand_int;
2419 else
2420 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2421 : lstrcmpW (leftOperand, rightOperand) > 0;
2424 return -1;
2427 /****************************************************************************
2428 * WCMD_if
2430 * Batch file conditional.
2432 * On entry, cmdlist will point to command containing the IF, and optionally
2433 * the first command to execute (if brackets not found)
2434 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2435 * If ('s were found, execute all within that bracket
2436 * Command may optionally be followed by an ELSE - need to skip instructions
2437 * in the else using the same logic
2439 * FIXME: Much more syntax checking needed!
2441 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2443 int negate; /* Negate condition */
2444 int test; /* Condition evaluation result */
2445 WCHAR condition[MAX_PATH], *command;
2446 static const WCHAR notW[] = {'n','o','t','\0'};
2447 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2448 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2449 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2450 static const WCHAR parmI[] = {'/','I','\0'};
2451 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2453 negate = !lstrcmpiW(param1,notW);
2454 strcpyW(condition, (negate ? param2 : param1));
2455 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2457 if (!lstrcmpiW (condition, errlvlW)) {
2458 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2459 WCHAR *endptr;
2460 long int param_int = strtolW(param, &endptr, 10);
2461 if (*endptr) goto syntax_err;
2462 test = ((long int)errorlevel >= param_int);
2463 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2465 else if (!lstrcmpiW (condition, existW)) {
2466 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2467 != INVALID_FILE_ATTRIBUTES);
2468 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2470 else if (!lstrcmpiW (condition, defdW)) {
2471 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2472 NULL, 0) > 0);
2473 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2475 else { /* comparison operation */
2476 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2477 WCHAR *paramStart;
2479 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, &paramStart, TRUE, FALSE));
2480 if (!*leftOperand)
2481 goto syntax_err;
2483 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2484 p = paramStart + strlenW(leftOperand);
2485 while (*p == ' ' || *p == '\t')
2486 p++;
2488 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2489 strcpyW(operator, eqeqW);
2490 else {
2491 strcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2492 if (!*operator) goto syntax_err;
2494 p += strlenW(operator);
2496 strcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2497 if (!*rightOperand)
2498 goto syntax_err;
2500 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2501 if (test == -1)
2502 goto syntax_err;
2504 p = paramStart + strlenW(rightOperand);
2505 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2508 /* Process rest of IF statement which is on the same line
2509 Note: This may process all or some of the cmdList (eg a GOTO) */
2510 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
2511 return;
2513 syntax_err:
2514 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2517 /****************************************************************************
2518 * WCMD_move
2520 * Move a file, directory tree or wildcarded set of files.
2523 void WCMD_move (void)
2525 int status;
2526 WIN32_FIND_DATAW fd;
2527 HANDLE hff;
2528 WCHAR input[MAX_PATH];
2529 WCHAR output[MAX_PATH];
2530 WCHAR drive[10];
2531 WCHAR dir[MAX_PATH];
2532 WCHAR fname[MAX_PATH];
2533 WCHAR ext[MAX_PATH];
2535 if (param1[0] == 0x00) {
2536 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2537 return;
2540 /* If no destination supplied, assume current directory */
2541 if (param2[0] == 0x00) {
2542 strcpyW(param2, dotW);
2545 /* If 2nd parm is directory, then use original filename */
2546 /* Convert partial path to full path */
2547 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2548 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2549 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2550 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2552 /* Split into components */
2553 WCMD_splitpath(input, drive, dir, fname, ext);
2555 hff = FindFirstFileW(input, &fd);
2556 if (hff == INVALID_HANDLE_VALUE)
2557 return;
2559 do {
2560 WCHAR dest[MAX_PATH];
2561 WCHAR src[MAX_PATH];
2562 DWORD attribs;
2563 BOOL ok = TRUE;
2565 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2567 /* Build src & dest name */
2568 strcpyW(src, drive);
2569 strcatW(src, dir);
2571 /* See if dest is an existing directory */
2572 attribs = GetFileAttributesW(output);
2573 if (attribs != INVALID_FILE_ATTRIBUTES &&
2574 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2575 strcpyW(dest, output);
2576 strcatW(dest, slashW);
2577 strcatW(dest, fd.cFileName);
2578 } else {
2579 strcpyW(dest, output);
2582 strcatW(src, fd.cFileName);
2584 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2585 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2587 /* If destination exists, prompt unless /Y supplied */
2588 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2589 BOOL force = FALSE;
2590 WCHAR copycmd[MAXSTRING];
2591 DWORD len;
2593 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2594 if (strstrW (quals, parmNoY))
2595 force = FALSE;
2596 else if (strstrW (quals, parmY))
2597 force = TRUE;
2598 else {
2599 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2600 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2601 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2602 && ! lstrcmpiW (copycmd, parmY));
2605 /* Prompt if overwriting */
2606 if (!force) {
2607 WCHAR* question;
2609 /* Ask for confirmation */
2610 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2611 ok = WCMD_ask_confirm(question, FALSE, NULL);
2612 LocalFree(question);
2614 /* So delete the destination prior to the move */
2615 if (ok) {
2616 if (!DeleteFileW(dest)) {
2617 WCMD_print_error ();
2618 errorlevel = 1;
2619 ok = FALSE;
2625 if (ok) {
2626 status = MoveFileW(src, dest);
2627 } else {
2628 status = 1; /* Anything other than 0 to prevent error msg below */
2631 if (!status) {
2632 WCMD_print_error ();
2633 errorlevel = 1;
2635 } while (FindNextFileW(hff, &fd) != 0);
2637 FindClose(hff);
2640 /****************************************************************************
2641 * WCMD_pause
2643 * Suspend execution of a batch script until a key is typed
2646 void WCMD_pause (void)
2648 DWORD oldmode;
2649 BOOL have_console;
2650 DWORD count;
2651 WCHAR key;
2652 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2654 have_console = GetConsoleMode(hIn, &oldmode);
2655 if (have_console)
2656 SetConsoleMode(hIn, 0);
2658 WCMD_output_asis(anykey);
2659 WCMD_ReadFile(hIn, &key, 1, &count);
2660 if (have_console)
2661 SetConsoleMode(hIn, oldmode);
2664 /****************************************************************************
2665 * WCMD_remove_dir
2667 * Delete a directory.
2670 void WCMD_remove_dir (WCHAR *args) {
2672 int argno = 0;
2673 int argsProcessed = 0;
2674 WCHAR *argN = args;
2675 static const WCHAR parmS[] = {'/','S','\0'};
2676 static const WCHAR parmQ[] = {'/','Q','\0'};
2678 /* Loop through all args */
2679 while (argN) {
2680 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2681 if (argN && argN[0] != '/') {
2682 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2683 wine_dbgstr_w(quals));
2684 argsProcessed++;
2686 /* If subdirectory search not supplied, just try to remove
2687 and report error if it fails (eg if it contains a file) */
2688 if (strstrW (quals, parmS) == NULL) {
2689 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2691 /* Otherwise use ShFileOp to recursively remove a directory */
2692 } else {
2694 SHFILEOPSTRUCTW lpDir;
2696 /* Ask first */
2697 if (strstrW (quals, parmQ) == NULL) {
2698 BOOL ok;
2699 WCHAR question[MAXSTRING];
2700 static const WCHAR fmt[] = {'%','s',' ','\0'};
2702 /* Ask for confirmation */
2703 wsprintfW(question, fmt, thisArg);
2704 ok = WCMD_ask_confirm(question, TRUE, NULL);
2706 /* Abort if answer is 'N' */
2707 if (!ok) return;
2710 /* Do the delete */
2711 lpDir.hwnd = NULL;
2712 lpDir.pTo = NULL;
2713 lpDir.pFrom = thisArg;
2714 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2715 lpDir.wFunc = FO_DELETE;
2717 /* SHFileOperationW needs file list with a double null termination */
2718 thisArg[lstrlenW(thisArg) + 1] = 0x00;
2720 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
2725 /* Handle no valid args */
2726 if (argsProcessed == 0) {
2727 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2728 return;
2733 /****************************************************************************
2734 * WCMD_rename
2736 * Rename a file.
2739 void WCMD_rename (void)
2741 int status;
2742 HANDLE hff;
2743 WIN32_FIND_DATAW fd;
2744 WCHAR input[MAX_PATH];
2745 WCHAR *dotDst = NULL;
2746 WCHAR drive[10];
2747 WCHAR dir[MAX_PATH];
2748 WCHAR fname[MAX_PATH];
2749 WCHAR ext[MAX_PATH];
2751 errorlevel = 0;
2753 /* Must be at least two args */
2754 if (param1[0] == 0x00 || param2[0] == 0x00) {
2755 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2756 errorlevel = 1;
2757 return;
2760 /* Destination cannot contain a drive letter or directory separator */
2761 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
2762 SetLastError(ERROR_INVALID_PARAMETER);
2763 WCMD_print_error();
2764 errorlevel = 1;
2765 return;
2768 /* Convert partial path to full path */
2769 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2770 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2771 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
2772 dotDst = strchrW(param2, '.');
2774 /* Split into components */
2775 WCMD_splitpath(input, drive, dir, fname, ext);
2777 hff = FindFirstFileW(input, &fd);
2778 if (hff == INVALID_HANDLE_VALUE)
2779 return;
2781 do {
2782 WCHAR dest[MAX_PATH];
2783 WCHAR src[MAX_PATH];
2784 WCHAR *dotSrc = NULL;
2785 int dirLen;
2787 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2789 /* FIXME: If dest name or extension is *, replace with filename/ext
2790 part otherwise use supplied name. This supports:
2791 ren *.fred *.jim
2792 ren jim.* fred.* etc
2793 However, windows has a more complex algorithm supporting eg
2794 ?'s and *'s mid name */
2795 dotSrc = strchrW(fd.cFileName, '.');
2797 /* Build src & dest name */
2798 strcpyW(src, drive);
2799 strcatW(src, dir);
2800 strcpyW(dest, src);
2801 dirLen = strlenW(src);
2802 strcatW(src, fd.cFileName);
2804 /* Build name */
2805 if (param2[0] == '*') {
2806 strcatW(dest, fd.cFileName);
2807 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
2808 } else {
2809 strcatW(dest, param2);
2810 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
2813 /* Build Extension */
2814 if (dotDst && (*(dotDst+1)=='*')) {
2815 if (dotSrc) strcatW(dest, dotSrc);
2816 } else if (dotDst) {
2817 if (dotDst) strcatW(dest, dotDst);
2820 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2821 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2823 status = MoveFileW(src, dest);
2825 if (!status) {
2826 WCMD_print_error ();
2827 errorlevel = 1;
2829 } while (FindNextFileW(hff, &fd) != 0);
2831 FindClose(hff);
2834 /*****************************************************************************
2835 * WCMD_dupenv
2837 * Make a copy of the environment.
2839 static WCHAR *WCMD_dupenv( const WCHAR *env )
2841 WCHAR *env_copy;
2842 int len;
2844 if( !env )
2845 return NULL;
2847 len = 0;
2848 while ( env[len] )
2849 len += (strlenW(&env[len]) + 1);
2851 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
2852 if (!env_copy)
2854 WINE_ERR("out of memory\n");
2855 return env_copy;
2857 memcpy (env_copy, env, len*sizeof (WCHAR));
2858 env_copy[len] = 0;
2860 return env_copy;
2863 /*****************************************************************************
2864 * WCMD_setlocal
2866 * setlocal pushes the environment onto a stack
2867 * Save the environment as unicode so we don't screw anything up.
2869 void WCMD_setlocal (const WCHAR *s) {
2870 WCHAR *env;
2871 struct env_stack *env_copy;
2872 WCHAR cwd[MAX_PATH];
2874 /* setlocal does nothing outside of batch programs */
2875 if (!context) return;
2877 /* DISABLEEXTENSIONS ignored */
2879 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2880 if( !env_copy )
2882 WINE_ERR ("out of memory\n");
2883 return;
2886 env = GetEnvironmentStringsW ();
2887 env_copy->strings = WCMD_dupenv (env);
2888 if (env_copy->strings)
2890 env_copy->batchhandle = context->h;
2891 env_copy->next = saved_environment;
2892 saved_environment = env_copy;
2894 /* Save the current drive letter */
2895 GetCurrentDirectoryW(MAX_PATH, cwd);
2896 env_copy->u.cwd = cwd[0];
2898 else
2899 LocalFree (env_copy);
2901 FreeEnvironmentStringsW (env);
2905 /*****************************************************************************
2906 * WCMD_endlocal
2908 * endlocal pops the environment off a stack
2909 * Note: When searching for '=', search from WCHAR position 1, to handle
2910 * special internal environment variables =C:, =D: etc
2912 void WCMD_endlocal (void) {
2913 WCHAR *env, *old, *p;
2914 struct env_stack *temp;
2915 int len, n;
2917 /* setlocal does nothing outside of batch programs */
2918 if (!context) return;
2920 /* setlocal needs a saved environment from within the same context (batch
2921 program) as it was saved in */
2922 if (!saved_environment || saved_environment->batchhandle != context->h)
2923 return;
2925 /* pop the old environment from the stack */
2926 temp = saved_environment;
2927 saved_environment = temp->next;
2929 /* delete the current environment, totally */
2930 env = GetEnvironmentStringsW ();
2931 old = WCMD_dupenv (GetEnvironmentStringsW ());
2932 len = 0;
2933 while (old[len]) {
2934 n = strlenW(&old[len]) + 1;
2935 p = strchrW(&old[len] + 1, '=');
2936 if (p)
2938 *p++ = 0;
2939 SetEnvironmentVariableW (&old[len], NULL);
2941 len += n;
2943 LocalFree (old);
2944 FreeEnvironmentStringsW (env);
2946 /* restore old environment */
2947 env = temp->strings;
2948 len = 0;
2949 while (env[len]) {
2950 n = strlenW(&env[len]) + 1;
2951 p = strchrW(&env[len] + 1, '=');
2952 if (p)
2954 *p++ = 0;
2955 SetEnvironmentVariableW (&env[len], p);
2957 len += n;
2960 /* Restore current drive letter */
2961 if (IsCharAlphaW(temp->u.cwd)) {
2962 WCHAR envvar[4];
2963 WCHAR cwd[MAX_PATH];
2964 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2966 wsprintfW(envvar, fmt, temp->u.cwd);
2967 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2968 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2969 SetCurrentDirectoryW(cwd);
2973 LocalFree (env);
2974 LocalFree (temp);
2977 /*****************************************************************************
2978 * WCMD_setshow_default
2980 * Set/Show the current default directory
2983 void WCMD_setshow_default (const WCHAR *args) {
2985 BOOL status;
2986 WCHAR string[1024];
2987 WCHAR cwd[1024];
2988 WCHAR *pos;
2989 WIN32_FIND_DATAW fd;
2990 HANDLE hff;
2991 static const WCHAR parmD[] = {'/','D','\0'};
2993 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
2995 /* Skip /D and trailing whitespace if on the front of the command line */
2996 if (CompareStringW(LOCALE_USER_DEFAULT,
2997 NORM_IGNORECASE | SORT_STRINGSORT,
2998 args, 2, parmD, -1) == CSTR_EQUAL) {
2999 args += 2;
3000 while (*args && (*args==' ' || *args=='\t'))
3001 args++;
3004 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3005 if (strlenW(args) == 0) {
3006 strcatW (cwd, newlineW);
3007 WCMD_output_asis (cwd);
3009 else {
3010 /* Remove any double quotes, which may be in the
3011 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3012 pos = string;
3013 while (*args) {
3014 if (*args != '"') *pos++ = *args;
3015 args++;
3017 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3018 pos--;
3019 *pos = 0x00;
3021 /* Search for appropriate directory */
3022 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3023 hff = FindFirstFileW(string, &fd);
3024 if (hff != INVALID_HANDLE_VALUE) {
3025 do {
3026 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3027 WCHAR fpath[MAX_PATH];
3028 WCHAR drive[10];
3029 WCHAR dir[MAX_PATH];
3030 WCHAR fname[MAX_PATH];
3031 WCHAR ext[MAX_PATH];
3032 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3034 /* Convert path into actual directory spec */
3035 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3036 WCMD_splitpath(fpath, drive, dir, fname, ext);
3038 /* Rebuild path */
3039 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3040 break;
3042 } while (FindNextFileW(hff, &fd) != 0);
3043 FindClose(hff);
3046 /* Change to that directory */
3047 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3049 status = SetCurrentDirectoryW(string);
3050 if (!status) {
3051 errorlevel = 1;
3052 WCMD_print_error ();
3053 return;
3054 } else {
3056 /* Save away the actual new directory, to store as current location */
3057 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3059 /* Restore old directory if drive letter would change, and
3060 CD x:\directory /D (or pushd c:\directory) not supplied */
3061 if ((strstrW(quals, parmD) == NULL) &&
3062 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3063 SetCurrentDirectoryW(cwd);
3067 /* Set special =C: type environment variable, for drive letter of
3068 change of directory, even if path was restored due to missing
3069 /D (allows changing drive letter when not resident on that
3070 drive */
3071 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3072 WCHAR env[4];
3073 strcpyW(env, equalW);
3074 memcpy(env+1, string, 2 * sizeof(WCHAR));
3075 env[3] = 0x00;
3076 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3077 SetEnvironmentVariableW(env, string);
3081 return;
3084 /****************************************************************************
3085 * WCMD_setshow_date
3087 * Set/Show the system date
3088 * FIXME: Can't change date yet
3091 void WCMD_setshow_date (void) {
3093 WCHAR curdate[64], buffer[64];
3094 DWORD count;
3095 static const WCHAR parmT[] = {'/','T','\0'};
3097 if (strlenW(param1) == 0) {
3098 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3099 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3100 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3101 if (strstrW (quals, parmT) == NULL) {
3102 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3103 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3104 if (count > 2) {
3105 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3109 else WCMD_print_error ();
3111 else {
3112 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3116 /****************************************************************************
3117 * WCMD_compare
3118 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3119 * the equals sign.
3121 static int WCMD_compare( const void *a, const void *b )
3123 int r;
3124 const WCHAR * const *str_a = a, * const *str_b = b;
3125 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3126 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3127 if( r == CSTR_LESS_THAN ) return -1;
3128 if( r == CSTR_GREATER_THAN ) return 1;
3129 return 0;
3132 /****************************************************************************
3133 * WCMD_setshow_sortenv
3135 * sort variables into order for display
3136 * Optionally only display those who start with a stub
3137 * returns the count displayed
3139 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3141 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3142 const WCHAR **str;
3144 if (stub) stublen = strlenW(stub);
3146 /* count the number of strings, and the total length */
3147 while ( s[len] ) {
3148 len += (strlenW(&s[len]) + 1);
3149 count++;
3152 /* add the strings to an array */
3153 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3154 if( !str )
3155 return 0;
3156 str[0] = s;
3157 for( i=1; i<count; i++ )
3158 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3160 /* sort the array */
3161 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3163 /* print it */
3164 for( i=0; i<count; i++ ) {
3165 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3166 NORM_IGNORECASE | SORT_STRINGSORT,
3167 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3168 /* Don't display special internal variables */
3169 if (str[i][0] != '=') {
3170 WCMD_output_asis(str[i]);
3171 WCMD_output_asis(newlineW);
3172 displayedcount++;
3177 LocalFree( str );
3178 return displayedcount;
3181 /****************************************************************************
3182 * WCMD_setshow_env
3184 * Set/Show the environment variables
3187 void WCMD_setshow_env (WCHAR *s) {
3189 LPVOID env;
3190 WCHAR *p;
3191 int status;
3192 static const WCHAR parmP[] = {'/','P','\0'};
3194 if (param1[0] == 0x00 && quals[0] == 0x00) {
3195 env = GetEnvironmentStringsW();
3196 WCMD_setshow_sortenv( env, NULL );
3197 return;
3200 /* See if /P supplied, and if so echo the prompt, and read in a reply */
3201 if (CompareStringW(LOCALE_USER_DEFAULT,
3202 NORM_IGNORECASE | SORT_STRINGSORT,
3203 s, 2, parmP, -1) == CSTR_EQUAL) {
3204 WCHAR string[MAXSTRING];
3205 DWORD count;
3207 s += 2;
3208 while (*s && (*s==' ' || *s=='\t')) s++;
3209 if (*s=='\"')
3210 WCMD_strip_quotes(s);
3212 /* If no parameter, or no '=' sign, return an error */
3213 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
3214 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3215 return;
3218 /* Output the prompt */
3219 *p++ = '\0';
3220 if (strlenW(p) != 0) WCMD_output_asis(p);
3222 /* Read the reply */
3223 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3224 if (count > 1) {
3225 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3226 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3227 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3228 wine_dbgstr_w(string));
3229 status = SetEnvironmentVariableW(s, string);
3232 } else {
3233 DWORD gle;
3235 if (*s=='\"')
3236 WCMD_strip_quotes(s);
3237 p = strchrW (s, '=');
3238 if (p == NULL) {
3239 env = GetEnvironmentStringsW();
3240 if (WCMD_setshow_sortenv( env, s ) == 0) {
3241 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
3242 errorlevel = 1;
3244 return;
3246 *p++ = '\0';
3248 if (strlenW(p) == 0) p = NULL;
3249 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3250 wine_dbgstr_w(p));
3251 status = SetEnvironmentVariableW(s, p);
3252 gle = GetLastError();
3253 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
3254 errorlevel = 1;
3255 } else if ((!status)) WCMD_print_error();
3256 else errorlevel = 0;
3260 /****************************************************************************
3261 * WCMD_setshow_path
3263 * Set/Show the path environment variable
3266 void WCMD_setshow_path (const WCHAR *args) {
3268 WCHAR string[1024];
3269 DWORD status;
3270 static const WCHAR pathW[] = {'P','A','T','H','\0'};
3271 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
3273 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
3274 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
3275 if (status != 0) {
3276 WCMD_output_asis ( pathEqW);
3277 WCMD_output_asis ( string);
3278 WCMD_output_asis ( newlineW);
3280 else {
3281 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
3284 else {
3285 if (*args == '=') args++; /* Skip leading '=' */
3286 status = SetEnvironmentVariableW(pathW, args);
3287 if (!status) WCMD_print_error();
3291 /****************************************************************************
3292 * WCMD_setshow_prompt
3294 * Set or show the command prompt.
3297 void WCMD_setshow_prompt (void) {
3299 WCHAR *s;
3300 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
3302 if (strlenW(param1) == 0) {
3303 SetEnvironmentVariableW(promptW, NULL);
3305 else {
3306 s = param1;
3307 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
3308 if (strlenW(s) == 0) {
3309 SetEnvironmentVariableW(promptW, NULL);
3311 else SetEnvironmentVariableW(promptW, s);
3315 /****************************************************************************
3316 * WCMD_setshow_time
3318 * Set/Show the system time
3319 * FIXME: Can't change time yet
3322 void WCMD_setshow_time (void) {
3324 WCHAR curtime[64], buffer[64];
3325 DWORD count;
3326 SYSTEMTIME st;
3327 static const WCHAR parmT[] = {'/','T','\0'};
3329 if (strlenW(param1) == 0) {
3330 GetLocalTime(&st);
3331 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
3332 curtime, sizeof(curtime)/sizeof(WCHAR))) {
3333 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
3334 if (strstrW (quals, parmT) == NULL) {
3335 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
3336 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3337 if (count > 2) {
3338 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3342 else WCMD_print_error ();
3344 else {
3345 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3349 /****************************************************************************
3350 * WCMD_shift
3352 * Shift batch parameters.
3353 * Optional /n says where to start shifting (n=0-8)
3356 void WCMD_shift (const WCHAR *args) {
3357 int start;
3359 if (context != NULL) {
3360 WCHAR *pos = strchrW(args, '/');
3361 int i;
3363 if (pos == NULL) {
3364 start = 0;
3365 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
3366 start = (*(pos+1) - '0');
3367 } else {
3368 SetLastError(ERROR_INVALID_PARAMETER);
3369 WCMD_print_error();
3370 return;
3373 WINE_TRACE("Shifting variables, starting at %d\n", start);
3374 for (i=start;i<=8;i++) {
3375 context -> shift_count[i] = context -> shift_count[i+1] + 1;
3377 context -> shift_count[9] = context -> shift_count[9] + 1;
3382 /****************************************************************************
3383 * WCMD_start
3385 void WCMD_start(const WCHAR *args)
3387 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
3388 '\\','s','t','a','r','t','.','e','x','e',0};
3389 WCHAR file[MAX_PATH];
3390 WCHAR *cmdline;
3391 STARTUPINFOW st;
3392 PROCESS_INFORMATION pi;
3394 GetWindowsDirectoryW( file, MAX_PATH );
3395 strcatW( file, exeW );
3396 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
3397 strcpyW( cmdline, file );
3398 strcatW( cmdline, spaceW );
3399 strcatW( cmdline, args );
3401 memset( &st, 0, sizeof(STARTUPINFOW) );
3402 st.cb = sizeof(STARTUPINFOW);
3404 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
3406 WaitForSingleObject( pi.hProcess, INFINITE );
3407 GetExitCodeProcess( pi.hProcess, &errorlevel );
3408 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
3409 CloseHandle(pi.hProcess);
3410 CloseHandle(pi.hThread);
3412 else
3414 SetLastError(ERROR_FILE_NOT_FOUND);
3415 WCMD_print_error ();
3416 errorlevel = 9009;
3418 HeapFree( GetProcessHeap(), 0, cmdline );
3421 /****************************************************************************
3422 * WCMD_title
3424 * Set the console title
3426 void WCMD_title (const WCHAR *args) {
3427 SetConsoleTitleW(args);
3430 /****************************************************************************
3431 * WCMD_type
3433 * Copy a file to standard output.
3436 void WCMD_type (WCHAR *args) {
3438 int argno = 0;
3439 WCHAR *argN = args;
3440 BOOL writeHeaders = FALSE;
3442 if (param1[0] == 0x00) {
3443 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3444 return;
3447 if (param2[0] != 0x00) writeHeaders = TRUE;
3449 /* Loop through all args */
3450 errorlevel = 0;
3451 while (argN) {
3452 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3454 HANDLE h;
3455 WCHAR buffer[512];
3456 DWORD count;
3458 if (!argN) break;
3460 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3461 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3462 FILE_ATTRIBUTE_NORMAL, NULL);
3463 if (h == INVALID_HANDLE_VALUE) {
3464 WCMD_print_error ();
3465 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3466 errorlevel = 1;
3467 } else {
3468 if (writeHeaders) {
3469 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
3470 WCMD_output(fmt, thisArg);
3472 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
3473 if (count == 0) break; /* ReadFile reports success on EOF! */
3474 buffer[count] = 0;
3475 WCMD_output_asis (buffer);
3477 CloseHandle (h);
3482 /****************************************************************************
3483 * WCMD_more
3485 * Output either a file or stdin to screen in pages
3488 void WCMD_more (WCHAR *args) {
3490 int argno = 0;
3491 WCHAR *argN = args;
3492 WCHAR moreStr[100];
3493 WCHAR moreStrPage[100];
3494 WCHAR buffer[512];
3495 DWORD count;
3496 static const WCHAR moreStart[] = {'-','-',' ','\0'};
3497 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
3498 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
3499 ')',' ','-','-','\n','\0'};
3500 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
3502 /* Prefix the NLS more with '-- ', then load the text */
3503 errorlevel = 0;
3504 strcpyW(moreStr, moreStart);
3505 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
3506 (sizeof(moreStr)/sizeof(WCHAR))-3);
3508 if (param1[0] == 0x00) {
3510 /* Wine implements pipes via temporary files, and hence stdin is
3511 effectively reading from the file. This means the prompts for
3512 more are satisfied by the next line from the input (file). To
3513 avoid this, ensure stdin is to the console */
3514 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
3515 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
3516 FILE_SHARE_READ, NULL, OPEN_EXISTING,
3517 FILE_ATTRIBUTE_NORMAL, 0);
3518 WINE_TRACE("No parms - working probably in pipe mode\n");
3519 SetStdHandle(STD_INPUT_HANDLE, hConIn);
3521 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
3522 once you get in this bit unless due to a pipe, its going to end badly... */
3523 wsprintfW(moreStrPage, moreFmt, moreStr);
3525 WCMD_enter_paged_mode(moreStrPage);
3526 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3527 if (count == 0) break; /* ReadFile reports success on EOF! */
3528 buffer[count] = 0;
3529 WCMD_output_asis (buffer);
3531 WCMD_leave_paged_mode();
3533 /* Restore stdin to what it was */
3534 SetStdHandle(STD_INPUT_HANDLE, hstdin);
3535 CloseHandle(hConIn);
3537 return;
3538 } else {
3539 BOOL needsPause = FALSE;
3541 /* Loop through all args */
3542 WINE_TRACE("Parms supplied - working through each file\n");
3543 WCMD_enter_paged_mode(moreStrPage);
3545 while (argN) {
3546 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3547 HANDLE h;
3549 if (!argN) break;
3551 if (needsPause) {
3553 /* Wait */
3554 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
3555 WCMD_leave_paged_mode();
3556 WCMD_output_asis(moreStrPage);
3557 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3558 WCMD_enter_paged_mode(moreStrPage);
3562 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3563 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3564 FILE_ATTRIBUTE_NORMAL, NULL);
3565 if (h == INVALID_HANDLE_VALUE) {
3566 WCMD_print_error ();
3567 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3568 errorlevel = 1;
3569 } else {
3570 ULONG64 curPos = 0;
3571 ULONG64 fileLen = 0;
3572 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
3574 /* Get the file size */
3575 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
3576 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
3578 needsPause = TRUE;
3579 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3580 if (count == 0) break; /* ReadFile reports success on EOF! */
3581 buffer[count] = 0;
3582 curPos += count;
3584 /* Update % count (would be used in WCMD_output_asis as prompt) */
3585 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
3587 WCMD_output_asis (buffer);
3589 CloseHandle (h);
3593 WCMD_leave_paged_mode();
3597 /****************************************************************************
3598 * WCMD_verify
3600 * Display verify flag.
3601 * FIXME: We don't actually do anything with the verify flag other than toggle
3602 * it...
3605 void WCMD_verify (const WCHAR *args) {
3607 int count;
3609 count = strlenW(args);
3610 if (count == 0) {
3611 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
3612 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
3613 return;
3615 if (lstrcmpiW(args, onW) == 0) {
3616 verify_mode = TRUE;
3617 return;
3619 else if (lstrcmpiW(args, offW) == 0) {
3620 verify_mode = FALSE;
3621 return;
3623 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
3626 /****************************************************************************
3627 * WCMD_version
3629 * Display version info.
3632 void WCMD_version (void) {
3634 WCMD_output_asis (version_string);
3638 /****************************************************************************
3639 * WCMD_volume
3641 * Display volume information (set_label = FALSE)
3642 * Additionally set volume label (set_label = TRUE)
3643 * Returns 1 on success, 0 otherwise
3646 int WCMD_volume(BOOL set_label, const WCHAR *path)
3648 DWORD count, serial;
3649 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
3650 BOOL status;
3652 if (strlenW(path) == 0) {
3653 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
3654 if (!status) {
3655 WCMD_print_error ();
3656 return 0;
3658 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
3659 &serial, NULL, NULL, NULL, 0);
3661 else {
3662 static const WCHAR fmt[] = {'%','s','\\','\0'};
3663 if ((path[1] != ':') || (strlenW(path) != 2)) {
3664 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
3665 return 0;
3667 wsprintfW (curdir, fmt, path);
3668 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
3669 &serial, NULL,
3670 NULL, NULL, 0);
3672 if (!status) {
3673 WCMD_print_error ();
3674 return 0;
3676 if (label[0] != '\0') {
3677 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
3678 curdir[0], label);
3680 else {
3681 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
3682 curdir[0]);
3684 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
3685 HIWORD(serial), LOWORD(serial));
3686 if (set_label) {
3687 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
3688 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3689 if (count > 1) {
3690 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3691 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3693 if (strlenW(path) != 0) {
3694 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
3696 else {
3697 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
3700 return 1;
3703 /**************************************************************************
3704 * WCMD_exit
3706 * Exit either the process, or just this batch program
3710 void WCMD_exit (CMD_LIST **cmdList) {
3712 static const WCHAR parmB[] = {'/','B','\0'};
3713 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
3715 if (context && lstrcmpiW(quals, parmB) == 0) {
3716 errorlevel = rc;
3717 context -> skip_rest = TRUE;
3718 *cmdList = NULL;
3719 } else {
3720 ExitProcess(rc);
3725 /*****************************************************************************
3726 * WCMD_assoc
3728 * Lists or sets file associations (assoc = TRUE)
3729 * Lists or sets file types (assoc = FALSE)
3731 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
3733 HKEY key;
3734 DWORD accessOptions = KEY_READ;
3735 WCHAR *newValue;
3736 LONG rc = ERROR_SUCCESS;
3737 WCHAR keyValue[MAXSTRING];
3738 DWORD valueLen = MAXSTRING;
3739 HKEY readKey;
3740 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
3741 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
3743 /* See if parameter includes '=' */
3744 errorlevel = 0;
3745 newValue = strchrW(args, '=');
3746 if (newValue) accessOptions |= KEY_WRITE;
3748 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
3749 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
3750 accessOptions, &key) != ERROR_SUCCESS) {
3751 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
3752 return;
3755 /* If no parameters then list all associations */
3756 if (*args == 0x00) {
3757 int index = 0;
3759 /* Enumerate all the keys */
3760 while (rc != ERROR_NO_MORE_ITEMS) {
3761 WCHAR keyName[MAXSTRING];
3762 DWORD nameLen;
3764 /* Find the next value */
3765 nameLen = MAXSTRING;
3766 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
3768 if (rc == ERROR_SUCCESS) {
3770 /* Only interested in extension ones if assoc, or others
3771 if not assoc */
3772 if ((keyName[0] == '.' && assoc) ||
3773 (!(keyName[0] == '.') && (!assoc)))
3775 WCHAR subkey[MAXSTRING];
3776 strcpyW(subkey, keyName);
3777 if (!assoc) strcatW(subkey, shOpCmdW);
3779 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3781 valueLen = sizeof(keyValue)/sizeof(WCHAR);
3782 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3783 WCMD_output_asis(keyName);
3784 WCMD_output_asis(equalW);
3785 /* If no default value found, leave line empty after '=' */
3786 if (rc == ERROR_SUCCESS) {
3787 WCMD_output_asis(keyValue);
3789 WCMD_output_asis(newlineW);
3790 RegCloseKey(readKey);
3796 } else {
3798 /* Parameter supplied - if no '=' on command line, its a query */
3799 if (newValue == NULL) {
3800 WCHAR *space;
3801 WCHAR subkey[MAXSTRING];
3803 /* Query terminates the parameter at the first space */
3804 strcpyW(keyValue, args);
3805 space = strchrW(keyValue, ' ');
3806 if (space) *space=0x00;
3808 /* Set up key name */
3809 strcpyW(subkey, keyValue);
3810 if (!assoc) strcatW(subkey, shOpCmdW);
3812 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3814 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3815 WCMD_output_asis(args);
3816 WCMD_output_asis(equalW);
3817 /* If no default value found, leave line empty after '=' */
3818 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
3819 WCMD_output_asis(newlineW);
3820 RegCloseKey(readKey);
3822 } else {
3823 WCHAR msgbuffer[MAXSTRING];
3825 /* Load the translated 'File association not found' */
3826 if (assoc) {
3827 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3828 } else {
3829 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3831 WCMD_output_stderr(msgbuffer, keyValue);
3832 errorlevel = 2;
3835 /* Not a query - its a set or clear of a value */
3836 } else {
3838 WCHAR subkey[MAXSTRING];
3840 /* Get pointer to new value */
3841 *newValue = 0x00;
3842 newValue++;
3844 /* Set up key name */
3845 strcpyW(subkey, args);
3846 if (!assoc) strcatW(subkey, shOpCmdW);
3848 /* If nothing after '=' then clear value - only valid for ASSOC */
3849 if (*newValue == 0x00) {
3851 if (assoc) rc = RegDeleteKeyW(key, args);
3852 if (assoc && rc == ERROR_SUCCESS) {
3853 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
3855 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
3856 WCMD_print_error();
3857 errorlevel = 2;
3859 } else {
3860 WCHAR msgbuffer[MAXSTRING];
3862 /* Load the translated 'File association not found' */
3863 if (assoc) {
3864 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
3865 sizeof(msgbuffer)/sizeof(WCHAR));
3866 } else {
3867 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
3868 sizeof(msgbuffer)/sizeof(WCHAR));
3870 WCMD_output_stderr(msgbuffer, keyValue);
3871 errorlevel = 2;
3874 /* It really is a set value = contents */
3875 } else {
3876 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
3877 accessOptions, NULL, &readKey, NULL);
3878 if (rc == ERROR_SUCCESS) {
3879 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
3880 (LPBYTE)newValue,
3881 sizeof(WCHAR) * (strlenW(newValue) + 1));
3882 RegCloseKey(readKey);
3885 if (rc != ERROR_SUCCESS) {
3886 WCMD_print_error();
3887 errorlevel = 2;
3888 } else {
3889 WCMD_output_asis(args);
3890 WCMD_output_asis(equalW);
3891 WCMD_output_asis(newValue);
3892 WCMD_output_asis(newlineW);
3898 /* Clean up */
3899 RegCloseKey(key);
3902 /****************************************************************************
3903 * WCMD_color
3905 * Colors the terminal screen.
3908 void WCMD_color (void) {
3910 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3911 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3913 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3914 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3915 return;
3918 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3920 COORD topLeft;
3921 DWORD screenSize;
3922 DWORD color = 0;
3924 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3926 topLeft.X = 0;
3927 topLeft.Y = 0;
3929 /* Convert the color hex digits */
3930 if (param1[0] == 0x00) {
3931 color = defaultColor;
3932 } else {
3933 color = strtoulW(param1, NULL, 16);
3936 /* Fail if fg == bg color */
3937 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3938 errorlevel = 1;
3939 return;
3942 /* Set the current screen contents and ensure all future writes
3943 remain this color */
3944 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3945 SetConsoleTextAttribute(hStdOut, color);