cmd: Move common error reporting code in WCMD_if.
[wine/wine-gecko.git] / programs / cmd / builtins.c
blobffdfe8d93fc979ca9d5dc9cac2b9c3aac0e10fe2
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'};
108 static HINSTANCE hinst;
109 struct env_stack *saved_environment;
110 static BOOL verify_mode = FALSE;
112 /**************************************************************************
113 * WCMD_ask_confirm
115 * Issue a message and ask for confirmation, waiting on a valid answer.
117 * Returns True if Y (or A) answer is selected
118 * If optionAll contains a pointer, ALL is allowed, and if answered
119 * set to TRUE
122 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
123 BOOL *optionAll) {
125 UINT msgid;
126 WCHAR confirm[MAXSTRING];
127 WCHAR options[MAXSTRING];
128 WCHAR Ybuffer[MAXSTRING];
129 WCHAR Nbuffer[MAXSTRING];
130 WCHAR Abuffer[MAXSTRING];
131 WCHAR answer[MAX_PATH] = {'\0'};
132 DWORD count = 0;
134 /* Load the translated valid answers */
135 if (showSureText)
136 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
137 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
138 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
139 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
140 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
141 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
143 /* Loop waiting on a valid answer */
144 if (optionAll)
145 *optionAll = FALSE;
146 while (1)
148 WCMD_output_asis (message);
149 if (showSureText)
150 WCMD_output_asis (confirm);
151 WCMD_output_asis (options);
152 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
153 answer[0] = toupperW(answer[0]);
154 if (answer[0] == Ybuffer[0])
155 return TRUE;
156 if (answer[0] == Nbuffer[0])
157 return FALSE;
158 if (optionAll && answer[0] == Abuffer[0])
160 *optionAll = TRUE;
161 return TRUE;
166 /****************************************************************************
167 * WCMD_clear_screen
169 * Clear the terminal screen.
172 void WCMD_clear_screen (void) {
174 /* Emulate by filling the screen from the top left to bottom right with
175 spaces, then moving the cursor to the top left afterwards */
176 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
177 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
179 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
181 COORD topLeft;
182 DWORD screenSize;
184 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
186 topLeft.X = 0;
187 topLeft.Y = 0;
188 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
189 SetConsoleCursorPosition(hStdOut, topLeft);
193 /****************************************************************************
194 * WCMD_change_tty
196 * Change the default i/o device (ie redirect STDin/STDout).
199 void WCMD_change_tty (void) {
201 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
205 /****************************************************************************
206 * WCMD_choice
210 void WCMD_choice (const WCHAR * args) {
212 static const WCHAR bellW[] = {7,0};
213 static const WCHAR commaW[] = {',',0};
214 static const WCHAR bracket_open[] = {'[',0};
215 static const WCHAR bracket_close[] = {']','?',0};
216 WCHAR answer[16];
217 WCHAR buffer[16];
218 WCHAR *ptr = NULL;
219 WCHAR *opt_c = NULL;
220 WCHAR *my_command = NULL;
221 WCHAR opt_default = 0;
222 DWORD opt_timeout = 0;
223 DWORD count;
224 DWORD oldmode;
225 DWORD have_console;
226 BOOL opt_n = FALSE;
227 BOOL opt_s = FALSE;
229 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
230 errorlevel = 0;
232 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
233 if (!my_command)
234 return;
236 ptr = WCMD_skip_leading_spaces(my_command);
237 while (*ptr == '/') {
238 switch (toupperW(ptr[1])) {
239 case 'C':
240 ptr += 2;
241 /* the colon is optional */
242 if (*ptr == ':')
243 ptr++;
245 if (!*ptr || isspaceW(*ptr)) {
246 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
247 HeapFree(GetProcessHeap(), 0, my_command);
248 return;
251 /* remember the allowed keys (overwrite previous /C option) */
252 opt_c = ptr;
253 while (*ptr && (!isspaceW(*ptr)))
254 ptr++;
256 if (*ptr) {
257 /* terminate allowed chars */
258 *ptr = 0;
259 ptr = WCMD_skip_leading_spaces(&ptr[1]);
261 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
262 break;
264 case 'N':
265 opt_n = TRUE;
266 ptr = WCMD_skip_leading_spaces(&ptr[2]);
267 break;
269 case 'S':
270 opt_s = TRUE;
271 ptr = WCMD_skip_leading_spaces(&ptr[2]);
272 break;
274 case 'T':
275 ptr = &ptr[2];
276 /* the colon is optional */
277 if (*ptr == ':')
278 ptr++;
280 opt_default = *ptr++;
282 if (!opt_default || (*ptr != ',')) {
283 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
284 HeapFree(GetProcessHeap(), 0, my_command);
285 return;
287 ptr++;
289 count = 0;
290 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
291 count++;
292 ptr++;
295 answer[count] = 0;
296 opt_timeout = atoiW(answer);
298 ptr = WCMD_skip_leading_spaces(ptr);
299 break;
301 default:
302 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
303 HeapFree(GetProcessHeap(), 0, my_command);
304 return;
308 if (opt_timeout)
309 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
311 if (have_console)
312 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
314 /* use default keys, when needed: localized versions of "Y"es and "No" */
315 if (!opt_c) {
316 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
317 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
318 opt_c = buffer;
319 buffer[2] = 0;
322 /* print the question, when needed */
323 if (*ptr)
324 WCMD_output_asis(ptr);
326 if (!opt_s) {
327 struprW(opt_c);
328 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
331 if (!opt_n) {
332 /* print a list of all allowed answers inside brackets */
333 WCMD_output_asis(bracket_open);
334 ptr = opt_c;
335 answer[1] = 0;
336 while ((answer[0] = *ptr++)) {
337 WCMD_output_asis(answer);
338 if (*ptr)
339 WCMD_output_asis(commaW);
341 WCMD_output_asis(bracket_close);
344 while (TRUE) {
346 /* FIXME: Add support for option /T */
347 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
349 if (!opt_s)
350 answer[0] = toupperW(answer[0]);
352 ptr = strchrW(opt_c, answer[0]);
353 if (ptr) {
354 WCMD_output_asis(answer);
355 WCMD_output_asis(newlineW);
356 if (have_console)
357 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
359 errorlevel = (ptr - opt_c) + 1;
360 WINE_TRACE("answer: %d\n", errorlevel);
361 HeapFree(GetProcessHeap(), 0, my_command);
362 return;
364 else
366 /* key not allowed: play the bell */
367 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
368 WCMD_output_asis(bellW);
373 /****************************************************************************
374 * WCMD_AppendEOF
376 * Adds an EOF onto the end of a file
377 * Returns TRUE on success
379 static BOOL WCMD_AppendEOF(WCHAR *filename)
381 HANDLE h;
383 char eof = '\x1a';
385 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
386 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
387 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
389 if (h == NULL) {
390 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
391 return FALSE;
392 } else {
393 SetFilePointer (h, 0, NULL, FILE_END);
394 if (!WriteFile(h, &eof, 1, NULL, NULL)) {
395 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
396 return FALSE;
398 CloseHandle(h);
400 return TRUE;
403 /****************************************************************************
404 * WCMD_ManualCopy
406 * Copies from a file
407 * optionally reading only until EOF (ascii copy)
408 * optionally appending onto an existing file (append)
409 * Returns TRUE on success
411 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
413 HANDLE in,out;
414 BOOL ok;
415 DWORD bytesread, byteswritten;
417 WINE_TRACE("ASCII Copying %s to %s (append?%d)\n",
418 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
420 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
421 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
422 if (in == NULL) {
423 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
424 return FALSE;
427 /* Open the output file, overwriting if not appending */
428 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
429 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
430 if (out == NULL) {
431 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
432 return FALSE;
435 /* Move to end of destination if we are going to append to it */
436 if (append) {
437 SetFilePointer(out, 0, NULL, FILE_END);
440 /* Loop copying data from source to destination until EOF read */
441 ok = TRUE;
444 char buffer[MAXSTRING];
446 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
447 if (ok) {
449 /* Stop at first EOF */
450 if (ascii) {
451 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
452 if (ptr) bytesread = (ptr - buffer);
455 if (bytesread) {
456 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
457 if (!ok || byteswritten != bytesread) {
458 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
459 wine_dbgstr_w(dstname), GetLastError());
462 } else {
463 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
464 wine_dbgstr_w(srcname), GetLastError());
466 } while (ok && bytesread > 0);
468 CloseHandle(out);
469 CloseHandle(in);
470 return ok;
473 /****************************************************************************
474 * WCMD_copy
476 * Copy a file or wildcarded set.
477 * For ascii/binary type copies, it gets complex:
478 * Syntax on command line is
479 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
480 * Where first /a or /b sets 'mode in operation' until another is found
481 * once another is found, it applies to the file preceding the /a or /b
482 * In addition each filename can contain wildcards
483 * To make matters worse, the + may be in the same parameter (i.e. no
484 * whitespace) or with whitespace separating it
486 * ASCII mode on read == read and stop at first EOF
487 * ASCII mode on write == append EOF to destination
488 * Binary == copy as-is
490 * Design of this is to build up a list of files which will be copied into a
491 * list, then work through the list file by file.
492 * If no destination is specified, it defaults to the name of the first file in
493 * the list, but the current directory.
497 void WCMD_copy(WCHAR * args) {
499 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
500 WCHAR *thisparam;
501 int argno = 0;
502 WCHAR *rawarg;
503 WIN32_FIND_DATAW fd;
504 HANDLE hff;
505 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
506 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
507 BOOL anyconcats = FALSE; /* Have we found any + options */
508 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
509 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
510 BOOL prompt; /* Prompt before overwriting */
511 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
512 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
513 BOOL status;
514 WCHAR copycmd[4];
515 DWORD len;
516 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
518 typedef struct _COPY_FILES
520 struct _COPY_FILES *next;
521 BOOL concatenate;
522 WCHAR *name;
523 int binarycopy;
524 } COPY_FILES;
525 COPY_FILES *sourcelist = NULL;
526 COPY_FILES *lastcopyentry = NULL;
527 COPY_FILES *destination = NULL;
528 COPY_FILES *thiscopy = NULL;
529 COPY_FILES *prevcopy = NULL;
531 /* Assume we were successful! */
532 errorlevel = 0;
534 /* If no args supplied at all, report an error */
535 if (param1[0] == 0x00) {
536 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
537 errorlevel = 1;
538 return;
541 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
543 /* Walk through all args, building up a list of files to process */
544 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
545 while (*(thisparam)) {
546 WCHAR *pos1, *pos2;
547 BOOL inquotes;
549 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
551 /* Handle switches */
552 if (*thisparam == '/') {
553 while (*thisparam == '/') {
554 thisparam++;
555 if (toupperW(*thisparam) == 'D') {
556 opt_d = TRUE;
557 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
558 } else if (toupperW(*thisparam) == 'Y') {
559 opt_y = TRUE;
560 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
561 opt_noty = TRUE;
562 } else if (toupperW(*thisparam) == 'V') {
563 opt_v = TRUE;
564 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
565 } else if (toupperW(*thisparam) == 'N') {
566 opt_n = TRUE;
567 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
568 } else if (toupperW(*thisparam) == 'Z') {
569 opt_z = TRUE;
570 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
571 } else if (toupperW(*thisparam) == 'A') {
572 if (binarymode != 0) {
573 binarymode = 0;
574 WINE_TRACE("Subsequent files will be handled as ASCII\n");
575 if (destination != NULL) {
576 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
577 destination->binarycopy = binarymode;
578 } else if (lastcopyentry != NULL) {
579 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
580 lastcopyentry->binarycopy = binarymode;
583 } else if (toupperW(*thisparam) == 'B') {
584 if (binarymode != 1) {
585 binarymode = 1;
586 WINE_TRACE("Subsequent files will be handled as binary\n");
587 if (destination != NULL) {
588 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
589 destination->binarycopy = binarymode;
590 } else if (lastcopyentry != NULL) {
591 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
592 lastcopyentry->binarycopy = binarymode;
595 } else {
596 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
598 thisparam++;
601 /* This parameter was purely switches, get the next one */
602 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
603 continue;
606 /* We have found something which is not a switch. If could be anything of the form
607 sourcefilename (which could be destination too)
608 + (when filename + filename syntex used)
609 sourcefilename+sourcefilename
610 +sourcefilename
611 +/b[tests show windows then ignores to end of parameter]
614 if (*thisparam=='+') {
615 if (lastcopyentry == NULL) {
616 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
617 errorlevel = 1;
618 goto exitreturn;
619 } else {
620 concatnextfilename = TRUE;
621 anyconcats = TRUE;
624 /* Move to next thing to process */
625 thisparam++;
626 if (*thisparam == 0x00)
627 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
628 continue;
631 /* We have found something to process - build a COPY_FILE block to store it */
632 thiscopy = HeapAlloc(GetProcessHeap(),0,sizeof(COPY_FILES));
633 if (thiscopy == NULL) goto exitreturn;
636 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
637 thiscopy->concatenate = concatnextfilename;
638 thiscopy->binarycopy = binarymode;
639 thiscopy->next = NULL;
641 /* Time to work out the name. Allocate at least enough space (deliberately too much to
642 leave space to append \* to the end) , then copy in character by character. Strip off
643 quotes if we find them. */
644 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
645 thiscopy->name = HeapAlloc(GetProcessHeap(),0,len*sizeof(WCHAR));
646 memset(thiscopy->name, 0x00, len);
648 pos1 = thisparam;
649 pos2 = thiscopy->name;
650 inquotes = FALSE;
651 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
652 if (*pos1 == '"') {
653 inquotes = !inquotes;
654 pos1++;
655 } else *pos2++ = *pos1++;
657 *pos2 = 0;
658 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
660 /* This is either the first source, concatenated subsequent source or destination */
661 if (sourcelist == NULL) {
662 WINE_TRACE("Adding as first source part\n");
663 sourcelist = thiscopy;
664 lastcopyentry = thiscopy;
665 } else if (concatnextfilename) {
666 WINE_TRACE("Adding to source file list to be concatenated\n");
667 lastcopyentry->next = thiscopy;
668 lastcopyentry = thiscopy;
669 } else if (destination == NULL) {
670 destination = thiscopy;
671 } else {
672 /* We have processed sources and destinations and still found more to do - invalid */
673 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
674 errorlevel = 1;
675 goto exitreturn;
677 concatnextfilename = FALSE;
679 /* We either need to process the rest of the parameter or move to the next */
680 if (*pos1 == '/' || *pos1 == '+') {
681 thisparam = pos1;
682 continue;
683 } else {
684 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
688 /* Ensure we have at least one source file */
689 if (!sourcelist) {
690 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
691 errorlevel = 1;
692 goto exitreturn;
695 /* Default whether automatic overwriting is on. If we are interactive then
696 we prompt by default, otherwise we overwrite by default
697 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
698 if (opt_noty) prompt = TRUE;
699 else if (opt_y) prompt = FALSE;
700 else {
701 /* By default, we will force the overwrite in batch mode and ask for
702 * confirmation in interactive mode. */
703 prompt = interactive;
704 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
705 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
706 * default behavior. */
707 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
708 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
709 if (!lstrcmpiW (copycmd, parmY))
710 prompt = FALSE;
711 else if (!lstrcmpiW (copycmd, parmNoY))
712 prompt = TRUE;
716 /* Calculate the destination now - if none supplied, its current dir +
717 filename of first file in list*/
718 if (destination == NULL) {
720 WINE_TRACE("No destination supplied, so need to calculate it\n");
721 strcpyW(destname, dotW);
722 strcatW(destname, slashW);
724 destination = HeapAlloc(GetProcessHeap(),0,sizeof(COPY_FILES));
725 if (destination == NULL) goto exitreturn;
726 destination->concatenate = FALSE; /* Not used for destination */
727 destination->binarycopy = binarymode;
728 destination->next = NULL; /* Not used for destination */
729 destination->name = NULL; /* To be filled in */
730 destisdirectory = TRUE;
732 } else {
733 WCHAR *filenamepart;
734 DWORD attributes;
736 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
738 /* Convert to fully qualified path/filename */
739 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
740 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
742 /* If parameter is a directory, ensure it ends in \ */
743 attributes = GetFileAttributesW(destname);
744 if ((destname[strlenW(destname) - 1] == '\\') ||
745 ((attributes != INVALID_FILE_ATTRIBUTES) &&
746 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
748 destisdirectory = TRUE;
749 if (!(destname[strlenW(destname) - 1] == '\\')) strcatW(destname, slashW);
750 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
754 /* Normally, the destination is the current directory unless we are
755 concatenating, in which case its current directory plus first filename.
756 Note that if the
757 In addition by default it is a binary copy unless concatenating, when
758 the copy defaults to an ascii copy (stop at EOF). We do not know the
759 first source part yet (until we search) so flag as needing filling in. */
761 if (anyconcats) {
762 /* We have found an a+b type syntax, so destination has to be a filename
763 and we need to default to ascii copying. If we have been supplied a
764 directory as the destination, we need to defer calculating the name */
765 if (destisdirectory) appendfirstsource = TRUE;
766 if (destination->binarycopy == -1) destination->binarycopy = 0;
768 } else if (!destisdirectory) {
769 /* We have been asked to copy to a filename. Default to ascii IF the
770 source contains wildcards (true even if only one match) */
771 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
772 anyconcats = TRUE; /* We really are concatenating to a single file */
773 if (destination->binarycopy == -1) {
774 destination->binarycopy = 0;
776 } else {
777 if (destination->binarycopy == -1) {
778 destination->binarycopy = 1;
783 /* Save away the destination name*/
784 HeapFree(GetProcessHeap(), 0, destination->name);
785 destination->name = WCMD_strdupW(destname);
786 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
787 wine_dbgstr_w(destname), appendfirstsource);
789 /* Now we need to walk the set of sources, and process each name we come to.
790 If anyconcats is true, we are writing to one file, otherwise we are using
791 the source name each time.
792 If destination exists, prompt for overwrite the first time (if concatenating
793 we ask each time until yes is answered)
794 The first source file we come across must exist (when wildcards expanded)
795 and if concatenating with overwrite prompts, each source file must exist
796 until a yes is answered. */
798 thiscopy = sourcelist;
799 prevcopy = NULL;
801 while (thiscopy != NULL) {
803 WCHAR srcpath[MAX_PATH];
804 WCHAR *filenamepart;
805 DWORD attributes;
807 /* If it was not explicit, we now know whether we are concatenating or not and
808 hence whether to copy as binary or ascii */
809 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
811 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
812 to where the filename portion begins (used for wildcart expansion. */
813 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
814 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
816 /* If parameter is a directory, ensure it ends in \* */
817 attributes = GetFileAttributesW(srcpath);
818 if (srcpath[strlenW(srcpath) - 1] == '\\') {
820 /* We need to know where the filename part starts, so append * and
821 recalculate the full resulting path */
822 strcatW(thiscopy->name, starW);
823 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
824 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
826 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
827 (attributes != INVALID_FILE_ATTRIBUTES) &&
828 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
830 /* We need to know where the filename part starts, so append \* and
831 recalculate the full resulting path */
832 strcatW(thiscopy->name, slashstarW);
833 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
834 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
837 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
838 wine_dbgstr_w(srcpath), anyconcats);
840 /* Loop through all source files */
841 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
842 hff = FindFirstFileW(srcpath, &fd);
843 if (hff != INVALID_HANDLE_VALUE) {
844 do {
845 WCHAR outname[MAX_PATH];
846 BOOL overwrite;
848 /* Skip . and .., and directories */
849 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
850 WINE_TRACE("Skipping directories\n");
851 } else {
853 /* Build final destination name */
854 strcpyW(outname, destination->name);
855 if (destisdirectory || appendfirstsource) strcatW(outname, fd.cFileName);
857 /* Build source name */
858 strcpyW(filenamepart, fd.cFileName);
860 /* Do we just overwrite */
861 overwrite = !prompt;
862 if (anyconcats && writtenoneconcat) {
863 overwrite = TRUE;
866 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
867 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
868 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
869 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
871 /* Prompt before overwriting */
872 if (!overwrite) {
873 DWORD attributes = GetFileAttributesW(outname);
874 if (attributes != INVALID_FILE_ATTRIBUTES) {
875 WCHAR* question;
876 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
877 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
878 LocalFree(question);
880 else overwrite = TRUE;
883 /* If we needed tyo save away the first filename, do it */
884 if (appendfirstsource && overwrite) {
885 HeapFree(GetProcessHeap(), 0, destination->name);
886 destination->name = WCMD_strdupW(outname);
887 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
888 appendfirstsource = FALSE;
889 destisdirectory = FALSE;
892 /* Do the copy as appropriate */
893 if (overwrite) {
894 if (anyconcats && writtenoneconcat) {
895 if (thiscopy->binarycopy) {
896 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
897 } else {
898 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
900 } else if (!thiscopy->binarycopy) {
901 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
902 } else {
903 status = CopyFileW(srcpath, outname, FALSE);
905 if (!status) {
906 WCMD_print_error ();
907 errorlevel = 1;
908 } else {
909 WINE_TRACE("Copied successfully\n");
910 if (anyconcats) writtenoneconcat = TRUE;
912 /* Append EOF if ascii destination and we are not going to add more onto the end
913 Note: Testing shows windows has an optimization whereas if you have a binary
914 copy of a file to a single destination (ie concatenation) then it does not add
915 the EOF, hence the check on the source copy type below. */
916 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
917 if (!WCMD_AppendEOF(outname)) {
918 WCMD_print_error ();
919 errorlevel = 1;
925 } while (FindNextFileW(hff, &fd) != 0);
926 FindClose (hff);
927 } else {
928 /* Error if the first file was not found */
929 if (!anyconcats || (anyconcats && !writtenoneconcat)) {
930 WCMD_print_error ();
931 errorlevel = 1;
935 /* Step on to the next supplied source */
936 thiscopy = thiscopy -> next;
939 /* Append EOF if ascii destination and we were concatenating */
940 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
941 if (!WCMD_AppendEOF(destination->name)) {
942 WCMD_print_error ();
943 errorlevel = 1;
947 /* Exit out of the routine, freeing any remaining allocated memory */
948 exitreturn:
950 thiscopy = sourcelist;
951 while (thiscopy != NULL) {
952 prevcopy = thiscopy;
953 /* Free up this block*/
954 thiscopy = thiscopy -> next;
955 HeapFree(GetProcessHeap(), 0, prevcopy->name);
956 HeapFree(GetProcessHeap(), 0, prevcopy);
959 /* Free up the destination memory */
960 if (destination) {
961 HeapFree(GetProcessHeap(), 0, destination->name);
962 HeapFree(GetProcessHeap(), 0, destination);
965 return;
968 /****************************************************************************
969 * WCMD_create_dir
971 * Create a directory (and, if needed, any intermediate directories).
973 * Modifies its argument by replacing slashes temporarily with nulls.
976 static BOOL create_full_path(WCHAR* path)
978 WCHAR *p, *start;
980 /* don't mess with drive letter portion of path, if any */
981 start = path;
982 if (path[1] == ':')
983 start = path+2;
985 /* Strip trailing slashes. */
986 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
987 *p = 0;
989 /* Step through path, creating intermediate directories as needed. */
990 /* First component includes drive letter, if any. */
991 p = start;
992 for (;;) {
993 DWORD rv;
994 /* Skip to end of component */
995 while (*p == '\\') p++;
996 while (*p && *p != '\\') p++;
997 if (!*p) {
998 /* path is now the original full path */
999 return CreateDirectoryW(path, NULL);
1001 /* Truncate path, create intermediate directory, and restore path */
1002 *p = 0;
1003 rv = CreateDirectoryW(path, NULL);
1004 *p = '\\';
1005 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1006 return FALSE;
1008 /* notreached */
1009 return FALSE;
1012 void WCMD_create_dir (WCHAR *args) {
1013 int argno = 0;
1014 WCHAR *argN = args;
1016 if (param1[0] == 0x00) {
1017 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1018 return;
1020 /* Loop through all args */
1021 while (TRUE) {
1022 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1023 if (!argN) break;
1024 if (!create_full_path(thisArg)) {
1025 WCMD_print_error ();
1026 errorlevel = 1;
1031 /* Parse the /A options given by the user on the commandline
1032 * into a bitmask of wanted attributes (*wantSet),
1033 * and a bitmask of unwanted attributes (*wantClear).
1035 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1036 static const WCHAR parmA[] = {'/','A','\0'};
1037 WCHAR *p;
1039 /* both are strictly 'out' parameters */
1040 *wantSet=0;
1041 *wantClear=0;
1043 /* For each /A argument */
1044 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1045 /* Skip /A itself */
1046 p += 2;
1048 /* Skip optional : */
1049 if (*p == ':') p++;
1051 /* For each of the attribute specifier chars to this /A option */
1052 for (; *p != 0 && *p != '/'; p++) {
1053 BOOL negate = FALSE;
1054 DWORD mask = 0;
1056 if (*p == '-') {
1057 negate=TRUE;
1058 p++;
1061 /* Convert the attribute specifier to a bit in one of the masks */
1062 switch (*p) {
1063 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1064 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1065 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1066 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1067 default:
1068 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1070 if (negate)
1071 *wantClear |= mask;
1072 else
1073 *wantSet |= mask;
1078 /* If filename part of parameter is * or *.*,
1079 * and neither /Q nor /P options were given,
1080 * prompt the user whether to proceed.
1081 * Returns FALSE if user says no, TRUE otherwise.
1082 * *pPrompted is set to TRUE if the user is prompted.
1083 * (If /P supplied, del will prompt for individual files later.)
1085 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1086 static const WCHAR parmP[] = {'/','P','\0'};
1087 static const WCHAR parmQ[] = {'/','Q','\0'};
1089 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1090 static const WCHAR anyExt[]= {'.','*','\0'};
1091 WCHAR drive[10];
1092 WCHAR dir[MAX_PATH];
1093 WCHAR fname[MAX_PATH];
1094 WCHAR ext[MAX_PATH];
1095 WCHAR fpath[MAX_PATH];
1097 /* Convert path into actual directory spec */
1098 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1099 WCMD_splitpath(fpath, drive, dir, fname, ext);
1101 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1102 if ((strcmpW(fname, starW) == 0) &&
1103 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1105 WCHAR question[MAXSTRING];
1106 static const WCHAR fmt[] = {'%','s',' ','\0'};
1108 /* Caller uses this to suppress "file not found" warning later */
1109 *pPrompted = TRUE;
1111 /* Ask for confirmation */
1112 wsprintfW(question, fmt, fpath);
1113 return WCMD_ask_confirm(question, TRUE, NULL);
1116 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1117 return TRUE;
1120 /* Helper function for WCMD_delete().
1121 * Deletes a single file, directory, or wildcard.
1122 * If /S was given, does it recursively.
1123 * Returns TRUE if a file was deleted.
1125 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1127 static const WCHAR parmP[] = {'/','P','\0'};
1128 static const WCHAR parmS[] = {'/','S','\0'};
1129 static const WCHAR parmF[] = {'/','F','\0'};
1130 DWORD wanted_attrs;
1131 DWORD unwanted_attrs;
1132 BOOL found = FALSE;
1133 WCHAR argCopy[MAX_PATH];
1134 WIN32_FIND_DATAW fd;
1135 HANDLE hff;
1136 WCHAR fpath[MAX_PATH];
1137 WCHAR *p;
1138 BOOL handleParm = TRUE;
1140 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1142 strcpyW(argCopy, thisArg);
1143 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1144 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1146 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1147 /* Skip this arg if user declines to delete *.* */
1148 return FALSE;
1151 /* First, try to delete in the current directory */
1152 hff = FindFirstFileW(argCopy, &fd);
1153 if (hff == INVALID_HANDLE_VALUE) {
1154 handleParm = FALSE;
1155 } else {
1156 found = TRUE;
1159 /* Support del <dirname> by just deleting all files dirname\* */
1160 if (handleParm
1161 && (strchrW(argCopy,'*') == NULL)
1162 && (strchrW(argCopy,'?') == NULL)
1163 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1165 WCHAR modifiedParm[MAX_PATH];
1166 static const WCHAR slashStar[] = {'\\','*','\0'};
1168 strcpyW(modifiedParm, argCopy);
1169 strcatW(modifiedParm, slashStar);
1170 FindClose(hff);
1171 found = TRUE;
1172 WCMD_delete_one(modifiedParm);
1174 } else if (handleParm) {
1176 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1177 strcpyW (fpath, argCopy);
1178 do {
1179 p = strrchrW (fpath, '\\');
1180 if (p != NULL) {
1181 *++p = '\0';
1182 strcatW (fpath, fd.cFileName);
1184 else strcpyW (fpath, fd.cFileName);
1185 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1186 BOOL ok;
1188 /* Handle attribute matching (/A) */
1189 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1190 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1192 /* /P means prompt for each file */
1193 if (ok && strstrW (quals, parmP) != NULL) {
1194 WCHAR* question;
1196 /* Ask for confirmation */
1197 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1198 ok = WCMD_ask_confirm(question, FALSE, NULL);
1199 LocalFree(question);
1202 /* Only proceed if ok to */
1203 if (ok) {
1205 /* If file is read only, and /A:r or /F supplied, delete it */
1206 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1207 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1208 strstrW (quals, parmF) != NULL)) {
1209 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1212 /* Now do the delete */
1213 if (!DeleteFileW(fpath)) WCMD_print_error ();
1217 } while (FindNextFileW(hff, &fd) != 0);
1218 FindClose (hff);
1221 /* Now recurse into all subdirectories handling the parameter in the same way */
1222 if (strstrW (quals, parmS) != NULL) {
1224 WCHAR thisDir[MAX_PATH];
1225 int cPos;
1227 WCHAR drive[10];
1228 WCHAR dir[MAX_PATH];
1229 WCHAR fname[MAX_PATH];
1230 WCHAR ext[MAX_PATH];
1232 /* Convert path into actual directory spec */
1233 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1234 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1236 strcpyW(thisDir, drive);
1237 strcatW(thisDir, dir);
1238 cPos = strlenW(thisDir);
1240 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1242 /* Append '*' to the directory */
1243 thisDir[cPos] = '*';
1244 thisDir[cPos+1] = 0x00;
1246 hff = FindFirstFileW(thisDir, &fd);
1248 /* Remove residual '*' */
1249 thisDir[cPos] = 0x00;
1251 if (hff != INVALID_HANDLE_VALUE) {
1252 DIRECTORY_STACK *allDirs = NULL;
1253 DIRECTORY_STACK *lastEntry = NULL;
1255 do {
1256 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1257 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1258 (strcmpW(fd.cFileName, dotW) != 0)) {
1260 DIRECTORY_STACK *nextDir;
1261 WCHAR subParm[MAX_PATH];
1263 /* Work out search parameter in sub dir */
1264 strcpyW (subParm, thisDir);
1265 strcatW (subParm, fd.cFileName);
1266 strcatW (subParm, slashW);
1267 strcatW (subParm, fname);
1268 strcatW (subParm, ext);
1269 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1271 /* Allocate memory, add to list */
1272 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
1273 if (allDirs == NULL) allDirs = nextDir;
1274 if (lastEntry != NULL) lastEntry->next = nextDir;
1275 lastEntry = nextDir;
1276 nextDir->next = NULL;
1277 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
1278 (strlenW(subParm)+1) * sizeof(WCHAR));
1279 strcpyW(nextDir->dirName, subParm);
1281 } while (FindNextFileW(hff, &fd) != 0);
1282 FindClose (hff);
1284 /* Go through each subdir doing the delete */
1285 while (allDirs != NULL) {
1286 DIRECTORY_STACK *tempDir;
1288 tempDir = allDirs->next;
1289 found |= WCMD_delete_one (allDirs->dirName);
1291 HeapFree(GetProcessHeap(),0,allDirs->dirName);
1292 HeapFree(GetProcessHeap(),0,allDirs);
1293 allDirs = tempDir;
1298 return found;
1301 /****************************************************************************
1302 * WCMD_delete
1304 * Delete a file or wildcarded set.
1306 * Note on /A:
1307 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1308 * - Each set is a pattern, eg /ahr /as-r means
1309 * readonly+hidden OR nonreadonly system files
1310 * - The '-' applies to a single field, ie /a:-hr means read only
1311 * non-hidden files
1314 BOOL WCMD_delete (WCHAR *args) {
1315 int argno;
1316 WCHAR *argN;
1317 BOOL argsProcessed = FALSE;
1318 BOOL foundAny = FALSE;
1320 errorlevel = 0;
1322 for (argno=0; ; argno++) {
1323 BOOL found;
1324 WCHAR *thisArg;
1326 argN = NULL;
1327 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1328 if (!argN)
1329 break; /* no more parameters */
1330 if (argN[0] == '/')
1331 continue; /* skip options */
1333 argsProcessed = TRUE;
1334 found = WCMD_delete_one(thisArg);
1335 if (!found) {
1336 errorlevel = 1;
1337 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1339 foundAny |= found;
1342 /* Handle no valid args */
1343 if (!argsProcessed)
1344 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1346 return foundAny;
1350 * WCMD_strtrim
1352 * Returns a trimmed version of s with all leading and trailing whitespace removed
1353 * Pre: s non NULL
1356 static WCHAR *WCMD_strtrim(const WCHAR *s)
1358 DWORD len = strlenW(s);
1359 const WCHAR *start = s;
1360 WCHAR* result;
1362 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
1363 return NULL;
1365 while (isspaceW(*start)) start++;
1366 if (*start) {
1367 const WCHAR *end = s + len - 1;
1368 while (end > start && isspaceW(*end)) end--;
1369 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1370 result[end - start + 1] = '\0';
1371 } else {
1372 result[0] = '\0';
1375 return result;
1378 /****************************************************************************
1379 * WCMD_echo
1381 * Echo input to the screen (or not). We don't try to emulate the bugs
1382 * in DOS (try typing "ECHO ON AGAIN" for an example).
1385 void WCMD_echo (const WCHAR *args)
1387 int count;
1388 const WCHAR *origcommand = args;
1389 WCHAR *trimmed;
1391 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1392 || args[0]==':' || args[0]==';')
1393 args++;
1395 trimmed = WCMD_strtrim(args);
1396 if (!trimmed) return;
1398 count = strlenW(trimmed);
1399 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1400 && origcommand[0]!=';') {
1401 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1402 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1403 return;
1406 if (lstrcmpiW(trimmed, onW) == 0)
1407 echo_mode = TRUE;
1408 else if (lstrcmpiW(trimmed, offW) == 0)
1409 echo_mode = FALSE;
1410 else {
1411 WCMD_output_asis (args);
1412 WCMD_output_asis (newlineW);
1414 HeapFree(GetProcessHeap(), 0, trimmed);
1417 /*****************************************************************************
1418 * WCMD_part_execute
1420 * Execute a command, and any && or bracketed follow on to the command. The
1421 * first command to be executed may not be at the front of the
1422 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1424 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1425 const WCHAR *variable, const WCHAR *value,
1426 BOOL isIF, BOOL executecmds)
1428 CMD_LIST *curPosition = *cmdList;
1429 int myDepth = (*cmdList)->bracketDepth;
1431 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1432 cmdList, wine_dbgstr_w(firstcmd),
1433 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1434 executecmds);
1436 /* Skip leading whitespace between condition and the command */
1437 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1439 /* Process the first command, if there is one */
1440 if (executecmds && firstcmd && *firstcmd) {
1441 WCHAR *command = WCMD_strdupW(firstcmd);
1442 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList, FALSE);
1443 HeapFree(GetProcessHeap(), 0, command);
1447 /* If it didn't move the position, step to next command */
1448 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1450 /* Process any other parts of the command */
1451 if (*cmdList) {
1452 BOOL processThese = executecmds;
1454 while (*cmdList) {
1455 static const WCHAR ifElse[] = {'e','l','s','e'};
1457 /* execute all appropriate commands */
1458 curPosition = *cmdList;
1460 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1461 *cmdList,
1462 (*cmdList)->prevDelim,
1463 (*cmdList)->bracketDepth, myDepth);
1465 /* Execute any statements appended to the line */
1466 /* FIXME: Only if previous call worked for && or failed for || */
1467 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1468 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1469 if (processThese && (*cmdList)->command) {
1470 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1471 value, cmdList, FALSE);
1473 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1475 /* Execute any appended to the statement with (...) */
1476 } else if ((*cmdList)->bracketDepth > myDepth) {
1477 if (processThese) {
1478 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value, FALSE);
1479 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1481 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1483 /* End of the command - does 'ELSE ' follow as the next command? */
1484 } else {
1485 if (isIF
1486 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1487 (*cmdList)->command)) {
1489 /* Swap between if and else processing */
1490 processThese = !processThese;
1492 /* Process the ELSE part */
1493 if (processThese) {
1494 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1495 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1497 /* Skip leading whitespace between condition and the command */
1498 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1499 if (*cmd) {
1500 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList, FALSE);
1503 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1504 } else {
1505 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1506 break;
1511 return;
1514 /*****************************************************************************
1515 * WCMD_parse_forf_options
1517 * Parses the for /f 'options', extracting the values and validating the
1518 * keywords. Note all keywords are optional.
1519 * Parameters:
1520 * options [I] The unparsed parameter string
1521 * eol [O] Set to the comment character (eol=x)
1522 * skip [O] Set to the number of lines to skip (skip=xx)
1523 * delims [O] Set to the token delimiters (delims=)
1524 * tokens [O] Set to the requested tokens, as provided (tokens=)
1525 * usebackq [O] Set to TRUE if usebackq found
1527 * Returns TRUE on success, FALSE on syntax error
1530 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1531 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1534 WCHAR *pos = options;
1535 int len = strlenW(pos);
1536 static const WCHAR eolW[] = {'e','o','l','='};
1537 static const WCHAR skipW[] = {'s','k','i','p','='};
1538 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1539 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1540 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1541 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1542 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1544 /* Initialize to defaults */
1545 strcpyW(delims, forf_defaultdelims);
1546 strcpyW(tokens, forf_defaulttokens);
1547 *eol = 0;
1548 *skip = 0;
1549 *usebackq = FALSE;
1551 /* Strip (optional) leading and trailing quotes */
1552 if ((*pos == '"') && (pos[len-1] == '"')) {
1553 pos[len-1] = 0;
1554 pos++;
1557 /* Process each keyword */
1558 while (pos && *pos) {
1559 if (*pos == ' ' || *pos == '\t') {
1560 pos++;
1562 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1563 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1564 pos, sizeof(eolW)/sizeof(WCHAR),
1565 eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1566 *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1567 pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1568 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1570 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1571 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1572 pos, sizeof(skipW)/sizeof(WCHAR),
1573 skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1574 WCHAR *nextchar = NULL;
1575 pos = pos + sizeof(skipW)/sizeof(WCHAR);
1576 *skip = strtoulW(pos, &nextchar, 0);
1577 WINE_TRACE("Found skip as %d lines\n", *skip);
1578 pos = nextchar;
1580 /* Save if usebackq semantics are in effect */
1581 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1582 pos, sizeof(usebackqW)/sizeof(WCHAR),
1583 usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1584 *usebackq = TRUE;
1585 pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1586 WINE_TRACE("Found usebackq\n");
1588 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1589 if you finish the optionsroot string with delims= otherwise the space is
1590 just a token delimiter! */
1591 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1592 pos, sizeof(delimsW)/sizeof(WCHAR),
1593 delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1594 int i=0;
1596 pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1597 while (*pos && *pos != ' ') {
1598 delims[i++] = *pos;
1599 pos++;
1601 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1602 delims[i++] = 0; /* Null terminate the delims */
1603 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1605 /* Save the tokens being requested */
1606 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1607 pos, sizeof(tokensW)/sizeof(WCHAR),
1608 tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1609 int i=0;
1611 pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1612 while (*pos && *pos != ' ') {
1613 tokens[i++] = *pos;
1614 pos++;
1616 tokens[i++] = 0; /* Null terminate the tokens */
1617 WINE_FIXME("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1619 } else {
1620 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1621 return FALSE;
1624 return TRUE;
1627 /*****************************************************************************
1628 * WCMD_add_dirstowalk
1630 * When recursing through directories (for /r), we need to add to the list of
1631 * directories still to walk, any subdirectories of the one we are processing.
1633 * Parameters
1634 * options [I] The remaining list of directories still to process
1636 * Note this routine inserts the subdirectories found between the entry being
1637 * processed, and any other directory still to be processed, mimicing what
1638 * Windows does
1640 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1641 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1642 WCHAR fullitem[MAX_PATH];
1643 WIN32_FIND_DATAW fd;
1644 HANDLE hff;
1646 /* Build a generic search and add all directories on the list of directories
1647 still to walk */
1648 strcpyW(fullitem, dirsToWalk->dirName);
1649 strcatW(fullitem, slashstarW);
1650 hff = FindFirstFileW(fullitem, &fd);
1651 if (hff != INVALID_HANDLE_VALUE) {
1652 do {
1653 WINE_TRACE("Looking for subdirectories\n");
1654 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1655 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1656 (strcmpW(fd.cFileName, dotW) != 0))
1658 /* Allocate memory, add to list */
1659 DIRECTORY_STACK *toWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1660 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1661 toWalk->next = remainingDirs->next;
1662 remainingDirs->next = toWalk;
1663 remainingDirs = toWalk;
1664 toWalk->dirName = HeapAlloc(GetProcessHeap(), 0,
1665 sizeof(WCHAR) *
1666 (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1667 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1668 strcatW(toWalk->dirName, slashW);
1669 strcatW(toWalk->dirName, fd.cFileName);
1670 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1671 toWalk, toWalk->next);
1673 } while (FindNextFileW(hff, &fd) != 0);
1674 WINE_TRACE("Finished adding all subdirectories\n");
1675 FindClose (hff);
1679 /**************************************************************************
1680 * WCMD_parse_line
1682 * When parsing file or string contents (for /f), once the string to parse
1683 * has been identified, handle the various options and call the do part
1684 * if appropriate.
1686 * Parameters:
1687 * cmdStart [I] - Identifies the list of commands making up the
1688 * for loop body (especially if brackets in use)
1689 * firstCmd [I] - The textual start of the command after the DO
1690 * which is within the first item of cmdStart
1691 * cmdEnd [O] - Identifies where to continue after the DO
1692 * variable [I] - The variable identified on the for line
1693 * buffer [I] - The string to parse
1694 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1695 * forf_skip [I/O] - How many lines to skip first
1696 * forf_eol [I] - The 'end of line' (comment) character
1697 * forf_delims [I] - The delimiters to use when breaking the string apart
1699 static void WCMD_parse_line(CMD_LIST *cmdStart,
1700 const WCHAR *firstCmd,
1701 CMD_LIST **cmdEnd,
1702 const WCHAR *variable,
1703 WCHAR *buffer,
1704 BOOL *doExecuted,
1705 int *forf_skip,
1706 WCHAR forf_eol,
1707 WCHAR *forf_delims) {
1709 WCHAR *parm, *where;
1711 /* Skip lines if requested */
1712 if (*forf_skip) {
1713 (*forf_skip)--;
1714 return;
1717 /* Extract the parameter */
1718 parm = WCMD_parameter_with_delims(buffer, 0, &where, FALSE, FALSE, forf_delims);
1719 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1720 wine_dbgstr_w(buffer));
1722 if (where && where[0] != forf_eol) {
1723 CMD_LIST *thisCmdStart = cmdStart;
1724 *doExecuted = TRUE;
1725 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1726 *cmdEnd = thisCmdStart;
1731 /**************************************************************************
1732 * WCMD_forf_getinputhandle
1734 * Return a file handle which can be used for reading the input lines,
1735 * either to a specific file (which may be quote delimited as we have to
1736 * read the parameters in raw mode) or to a command which we need to
1737 * execute. The command being executed runs in its own shell and stores
1738 * its data in a temporary file.
1740 * Parameters:
1741 * usebackq [I] - Indicates whether usebackq is in effect or not
1742 * itemStr [I] - The item to be handled, either a filename or
1743 * whole command string to execute
1744 * iscmd [I] - Identifies whether this is a command or not
1746 * Returns a file handle which can be used to read the input lines from.
1748 HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
1749 WCHAR temp_str[MAX_PATH];
1750 WCHAR temp_file[MAX_PATH];
1751 WCHAR temp_cmd[MAXSTRING];
1752 HANDLE hinput = INVALID_HANDLE_VALUE;
1753 static const WCHAR redirOutW[] = {'>','%','s','\0'};
1754 static const WCHAR cmdW[] = {'C','M','D','\0'};
1755 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
1756 '/','C',' ','"','%','s','"','\0'};
1758 /* Remove leading and trailing character */
1759 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
1760 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
1761 (!iscmd && (itemstr[0] == '"' && usebackq)))
1763 itemstr[strlenW(itemstr)-1] = 0x00;
1764 itemstr++;
1767 if (iscmd) {
1768 /* Get temp filename */
1769 GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
1770 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
1772 /* Redirect output to the temporary file */
1773 wsprintfW(temp_str, redirOutW, temp_file);
1774 wsprintfW(temp_cmd, cmdslashcW, itemstr);
1775 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
1776 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
1777 WCMD_execute (temp_cmd, temp_str, NULL, NULL, NULL, FALSE);
1779 /* Open the file, read line by line and process */
1780 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1781 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
1783 } else {
1784 /* Open the file, read line by line and process */
1785 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
1786 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
1787 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1789 return hinput;
1792 /**************************************************************************
1793 * WCMD_for
1795 * Batch file loop processing.
1797 * On entry: cmdList contains the syntax up to the set
1798 * next cmdList and all in that bracket contain the set data
1799 * next cmdlist contains the DO cmd
1800 * following that is either brackets or && entries (as per if)
1804 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1806 WIN32_FIND_DATAW fd;
1807 HANDLE hff;
1808 int i;
1809 static const WCHAR inW[] = {'i','n'};
1810 static const WCHAR doW[] = {'d','o'};
1811 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1812 WCHAR variable[4];
1813 WCHAR *firstCmd;
1814 int thisDepth;
1815 WCHAR optionsRoot[MAX_PATH];
1816 DIRECTORY_STACK *dirsToWalk = NULL;
1817 BOOL expandDirs = FALSE;
1818 BOOL useNumbers = FALSE;
1819 BOOL doFileset = FALSE;
1820 BOOL doRecurse = FALSE;
1821 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
1822 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1823 int itemNum;
1824 CMD_LIST *thisCmdStart;
1825 int parameterNo = 0;
1826 WCHAR forf_eol = 0;
1827 int forf_skip = 0;
1828 WCHAR forf_delims[256];
1829 WCHAR forf_tokens[MAXSTRING];
1830 BOOL forf_usebackq = FALSE;
1832 /* Handle optional qualifiers (multiple are allowed) */
1833 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
1835 optionsRoot[0] = 0;
1836 while (thisArg && *thisArg == '/') {
1837 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
1838 thisArg++;
1839 switch (toupperW(*thisArg)) {
1840 case 'D': expandDirs = TRUE; break;
1841 case 'L': useNumbers = TRUE; break;
1843 /* Recursive is special case - /R can have an optional path following it */
1844 /* filenamesets are another special case - /F can have an optional options following it */
1845 case 'R':
1846 case 'F':
1848 /* When recursing directories, use current directory as the starting point unless
1849 subsequently overridden */
1850 doRecurse = (toupperW(*thisArg) == 'R');
1851 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
1853 doFileset = (toupperW(*thisArg) == 'F');
1855 /* Retrieve next parameter to see if is root/options (raw form required
1856 with for /f, or unquoted in for /r) */
1857 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
1859 /* Next parm is either qualifier, path/options or variable -
1860 only care about it if it is the path/options */
1861 if (thisArg && *thisArg != '/' && *thisArg != '%') {
1862 parameterNo++;
1863 strcpyW(optionsRoot, thisArg);
1865 break;
1867 default:
1868 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
1871 /* Step to next token */
1872 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
1875 /* Ensure line continues with variable */
1876 if (!*thisArg || *thisArg != '%') {
1877 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1878 return;
1881 /* With for /f parse the options if provided */
1882 if (doFileset) {
1883 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
1884 forf_delims, forf_tokens, &forf_usebackq))
1886 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1887 return;
1890 /* Set up the list of directories to recurse if we are going to */
1891 } else if (doRecurse) {
1892 /* Allocate memory, add to list */
1893 dirsToWalk = HeapAlloc(GetProcessHeap(), 0, sizeof(DIRECTORY_STACK));
1894 dirsToWalk->next = NULL;
1895 dirsToWalk->dirName = HeapAlloc(GetProcessHeap(),0,
1896 (strlenW(optionsRoot) + 1) * sizeof(WCHAR));
1897 strcpyW(dirsToWalk->dirName, optionsRoot);
1898 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
1901 /* Variable should follow */
1902 strcpyW(variable, thisArg);
1903 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1905 /* Ensure line continues with IN */
1906 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
1907 if (!thisArg
1908 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1909 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
1910 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
1911 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1912 return;
1915 /* Save away where the set of data starts and the variable */
1916 thisDepth = (*cmdList)->bracketDepth;
1917 *cmdList = (*cmdList)->nextcommand;
1918 setStart = (*cmdList);
1920 /* Skip until the close bracket */
1921 WINE_TRACE("Searching %p as the set\n", *cmdList);
1922 while (*cmdList &&
1923 (*cmdList)->command != NULL &&
1924 (*cmdList)->bracketDepth > thisDepth) {
1925 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1926 *cmdList = (*cmdList)->nextcommand;
1929 /* Skip the close bracket, if there is one */
1930 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1932 /* Syntax error if missing close bracket, or nothing following it
1933 and once we have the complete set, we expect a DO */
1934 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1935 if ((*cmdList == NULL)
1936 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1938 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1939 return;
1942 cmdEnd = *cmdList;
1944 /* Loop repeatedly per-directory we are potentially walking, when in for /r
1945 mode, or once for the rest of the time. */
1946 do {
1948 /* Save away the starting position for the commands (and offset for the
1949 first one) */
1950 cmdStart = *cmdList;
1951 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1952 itemNum = 0;
1954 /* If we are recursing directories (ie /R), add all sub directories now, then
1955 prefix the root when searching for the item */
1956 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
1958 thisSet = setStart;
1959 /* Loop through all set entries */
1960 while (thisSet &&
1961 thisSet->command != NULL &&
1962 thisSet->bracketDepth >= thisDepth) {
1964 /* Loop through all entries on the same line */
1965 WCHAR *item;
1966 WCHAR *itemStart;
1967 WCHAR buffer[MAXSTRING];
1969 WINE_TRACE("Processing for set %p\n", thisSet);
1970 i = 0;
1971 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
1974 * If the parameter within the set has a wildcard then search for matching files
1975 * otherwise do a literal substitution.
1977 static const WCHAR wildcards[] = {'*','?','\0'};
1978 thisCmdStart = cmdStart;
1980 itemNum++;
1981 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1983 if (!useNumbers && !doFileset) {
1984 WCHAR fullitem[MAX_PATH];
1986 /* Now build the item to use / search for in the specified directory,
1987 as it is fully qualified in the /R case */
1988 if (dirsToWalk) {
1989 strcpyW(fullitem, dirsToWalk->dirName);
1990 strcatW(fullitem, slashW);
1991 strcatW(fullitem, item);
1992 } else {
1993 strcpyW(fullitem, item);
1996 if (strpbrkW (fullitem, wildcards)) {
1998 hff = FindFirstFileW(fullitem, &fd);
1999 if (hff != INVALID_HANDLE_VALUE) {
2000 do {
2001 BOOL isDirectory = FALSE;
2003 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2005 /* Handle as files or dirs appropriately, but ignore . and .. */
2006 if (isDirectory == expandDirs &&
2007 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2008 (strcmpW(fd.cFileName, dotW) != 0))
2010 thisCmdStart = cmdStart;
2011 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2013 if (doRecurse) {
2014 strcpyW(fullitem, dirsToWalk->dirName);
2015 strcatW(fullitem, slashW);
2016 strcatW(fullitem, fd.cFileName);
2017 } else {
2018 strcpyW(fullitem, fd.cFileName);
2020 doExecuted = TRUE;
2021 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
2022 fullitem, FALSE, TRUE);
2023 cmdEnd = thisCmdStart;
2025 } while (FindNextFileW(hff, &fd) != 0);
2026 FindClose (hff);
2028 } else {
2029 doExecuted = TRUE;
2030 WCMD_part_execute(&thisCmdStart, firstCmd, variable, fullitem, FALSE, TRUE);
2031 cmdEnd = thisCmdStart;
2034 } else if (useNumbers) {
2035 /* Convert the first 3 numbers to signed longs and save */
2036 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2037 /* else ignore them! */
2039 /* Filesets - either a list of files, or a command to run and parse the output */
2040 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2041 (forf_usebackq && *itemStart != '\''))) {
2043 HANDLE input;
2044 WCHAR *itemparm;
2046 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2047 wine_dbgstr_w(item));
2049 /* If backquote or single quote, we need to launch that command
2050 and parse the results - use a temporary file */
2051 if ((forf_usebackq && *itemStart == '`') ||
2052 (!forf_usebackq && *itemStart == '\'')) {
2054 /* Use itemstart because the command is the whole set, not just the first token */
2055 itemparm = itemStart;
2056 } else {
2058 /* Use item because the file to process is just the first item in the set */
2059 itemparm = item;
2061 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2063 /* Process the input file */
2064 if (input == INVALID_HANDLE_VALUE) {
2065 WCMD_print_error ();
2066 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2067 errorlevel = 1;
2068 return; /* FOR loop aborts at first failure here */
2070 } else {
2072 /* Read line by line until end of file */
2073 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2074 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable, buffer, &doExecuted,
2075 &forf_skip, forf_eol, forf_delims);
2076 buffer[0] = 0;
2078 CloseHandle (input);
2081 /* When we have processed the item as a whole command, abort future set processing */
2082 if (itemparm==itemStart) {
2083 thisSet = NULL;
2084 break;
2087 /* Filesets - A string literal */
2088 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2089 (forf_usebackq && *itemStart == '\''))) {
2091 /* Remove leading and trailing character, ready to parse with delims= delimiters
2092 Note that the last quote is removed from the set and the string terminates
2093 there to mimic windows */
2094 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2095 if (strend) {
2096 *strend = 0x00;
2097 itemStart++;
2100 /* Copy the item away from the global buffer used by WCMD_parameter */
2101 strcpyW(buffer, itemStart);
2102 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable, buffer, &doExecuted,
2103 &forf_skip, forf_eol, forf_delims);
2105 /* Only one string can be supplied in the whole set, abort future set processing */
2106 thisSet = NULL;
2107 break;
2110 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2111 i++;
2114 /* Move onto the next set line */
2115 if (thisSet) thisSet = thisSet->nextcommand;
2118 /* If /L is provided, now run the for loop */
2119 if (useNumbers) {
2120 WCHAR thisNum[20];
2121 static const WCHAR fmt[] = {'%','d','\0'};
2123 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2124 numbers[0], numbers[2], numbers[1]);
2125 for (i=numbers[0];
2126 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2127 i=i + numbers[1]) {
2129 sprintfW(thisNum, fmt, i);
2130 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2132 thisCmdStart = cmdStart;
2133 doExecuted = TRUE;
2134 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
2136 cmdEnd = thisCmdStart;
2139 /* If we are walking directories, move on to any which remain */
2140 if (dirsToWalk != NULL) {
2141 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2142 HeapFree(GetProcessHeap(), 0, dirsToWalk->dirName);
2143 HeapFree(GetProcessHeap(), 0, dirsToWalk);
2144 dirsToWalk = nextDir;
2145 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2146 wine_dbgstr_w(dirsToWalk->dirName));
2147 else WINE_TRACE("Finished all directories.\n");
2150 } while (dirsToWalk != NULL);
2152 /* Now skip over the do part if we did not perform the for loop so far.
2153 We store in cmdEnd the next command after the do block, but we only
2154 know this if something was run. If it has not been, we need to calculate
2155 it. */
2156 if (!doExecuted) {
2157 thisCmdStart = cmdStart;
2158 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2159 WCMD_part_execute(&thisCmdStart, firstCmd, NULL, NULL, FALSE, FALSE);
2160 cmdEnd = thisCmdStart;
2163 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2164 all processing, OR it should be pointing to the end of && processing OR
2165 it should be pointing at the NULL end of bracket for the DO. The return
2166 value needs to be the NEXT command to execute, which it either is, or
2167 we need to step over the closing bracket */
2168 *cmdList = cmdEnd;
2169 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2172 /**************************************************************************
2173 * WCMD_give_help
2175 * Simple on-line help. Help text is stored in the resource file.
2178 void WCMD_give_help (const WCHAR *args)
2180 size_t i;
2182 args = WCMD_skip_leading_spaces((WCHAR*) args);
2183 if (strlenW(args) == 0) {
2184 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2186 else {
2187 /* Display help message for builtin commands */
2188 for (i=0; i<=WCMD_EXIT; i++) {
2189 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2190 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2191 WCMD_output_asis (WCMD_LoadMessage(i));
2192 return;
2195 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2196 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2197 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2198 args, -1, externals[i], -1) == CSTR_EQUAL) {
2199 WCHAR cmd[128];
2200 static const WCHAR helpW[] = {' ', '/','?','\0'};
2201 strcpyW(cmd, args);
2202 strcatW(cmd, helpW);
2203 WCMD_run_program(cmd, FALSE);
2204 return;
2207 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2209 return;
2212 /****************************************************************************
2213 * WCMD_go_to
2215 * Batch file jump instruction. Not the most efficient algorithm ;-)
2216 * Prints error message if the specified label cannot be found - the file pointer is
2217 * then at EOF, effectively stopping the batch file.
2218 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2221 void WCMD_goto (CMD_LIST **cmdList) {
2223 WCHAR string[MAX_PATH];
2224 WCHAR current[MAX_PATH];
2226 /* Do not process any more parts of a processed multipart or multilines command */
2227 if (cmdList) *cmdList = NULL;
2229 if (context != NULL) {
2230 WCHAR *paramStart = param1, *str;
2231 static const WCHAR eofW[] = {':','e','o','f','\0'};
2233 if (param1[0] == 0x00) {
2234 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2235 return;
2238 /* Handle special :EOF label */
2239 if (lstrcmpiW (eofW, param1) == 0) {
2240 context -> skip_rest = TRUE;
2241 return;
2244 /* Support goto :label as well as goto label */
2245 if (*paramStart == ':') paramStart++;
2247 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2248 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2249 str = string;
2250 while (isspaceW (*str)) str++;
2251 if (*str == ':') {
2252 DWORD index = 0;
2253 str++;
2254 while (((current[index] = str[index])) && (!isspaceW (current[index])))
2255 index++;
2257 /* ignore space at the end */
2258 current[index] = 0;
2259 if (lstrcmpiW (current, paramStart) == 0) return;
2262 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2264 return;
2267 /*****************************************************************************
2268 * WCMD_pushd
2270 * Push a directory onto the stack
2273 void WCMD_pushd (const WCHAR *args)
2275 struct env_stack *curdir;
2276 WCHAR *thisdir;
2277 static const WCHAR parmD[] = {'/','D','\0'};
2279 if (strchrW(args, '/') != NULL) {
2280 SetLastError(ERROR_INVALID_PARAMETER);
2281 WCMD_print_error();
2282 return;
2285 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2286 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2287 if( !curdir || !thisdir ) {
2288 LocalFree(curdir);
2289 LocalFree(thisdir);
2290 WINE_ERR ("out of memory\n");
2291 return;
2294 /* Change directory using CD code with /D parameter */
2295 strcpyW(quals, parmD);
2296 GetCurrentDirectoryW (1024, thisdir);
2297 errorlevel = 0;
2298 WCMD_setshow_default(args);
2299 if (errorlevel) {
2300 LocalFree(curdir);
2301 LocalFree(thisdir);
2302 return;
2303 } else {
2304 curdir -> next = pushd_directories;
2305 curdir -> strings = thisdir;
2306 if (pushd_directories == NULL) {
2307 curdir -> u.stackdepth = 1;
2308 } else {
2309 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2311 pushd_directories = curdir;
2316 /*****************************************************************************
2317 * WCMD_popd
2319 * Pop a directory from the stack
2322 void WCMD_popd (void) {
2323 struct env_stack *temp = pushd_directories;
2325 if (!pushd_directories)
2326 return;
2328 /* pop the old environment from the stack, and make it the current dir */
2329 pushd_directories = temp->next;
2330 SetCurrentDirectoryW(temp->strings);
2331 LocalFree (temp->strings);
2332 LocalFree (temp);
2335 /****************************************************************************
2336 * WCMD_if
2338 * Batch file conditional.
2340 * On entry, cmdlist will point to command containing the IF, and optionally
2341 * the first command to execute (if brackets not found)
2342 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2343 * If ('s were found, execute all within that bracket
2344 * Command may optionally be followed by an ELSE - need to skip instructions
2345 * in the else using the same logic
2347 * FIXME: Much more syntax checking needed!
2349 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2351 int negate; /* Negate condition */
2352 int test; /* Condition evaluation result */
2353 WCHAR condition[MAX_PATH], *command, *s;
2354 static const WCHAR notW[] = {'n','o','t','\0'};
2355 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2356 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2357 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2358 static const WCHAR eqeqW[] = {'=','=','\0'};
2359 static const WCHAR parmI[] = {'/','I','\0'};
2360 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2362 negate = !lstrcmpiW(param1,notW);
2363 strcpyW(condition, (negate ? param2 : param1));
2364 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2366 if (!lstrcmpiW (condition, errlvlW)) {
2367 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2368 WCHAR *endptr;
2369 long int param_int = strtolW(param, &endptr, 10);
2370 if (*endptr) goto syntax_err;
2371 test = ((long int)errorlevel >= param_int);
2372 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2374 else if (!lstrcmpiW (condition, existW)) {
2375 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2376 != INVALID_FILE_ATTRIBUTES);
2377 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2379 else if (!lstrcmpiW (condition, defdW)) {
2380 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2381 NULL, 0) > 0);
2382 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2384 else if ((s = strstrW (p, eqeqW))) {
2385 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
2386 WCHAR *leftPart, *rightPart;
2387 unsigned int leftPartLen, rightPartLen;
2388 s += 2;
2389 leftPartLen = strlenW(WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, TRUE, FALSE));
2390 rightPartLen = strlenW(WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, TRUE, FALSE));
2391 test = caseInsensitive
2392 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
2393 leftPart, leftPartLen,
2394 rightPart, rightPartLen) == CSTR_EQUAL)
2395 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
2396 leftPart, leftPartLen,
2397 rightPart, rightPartLen) == CSTR_EQUAL);
2398 WCMD_parameter(s, 1, &command, FALSE, FALSE);
2400 else goto syntax_err;
2402 /* Process rest of IF statement which is on the same line
2403 Note: This may process all or some of the cmdList (eg a GOTO) */
2404 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
2405 return;
2407 syntax_err:
2408 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2411 /****************************************************************************
2412 * WCMD_move
2414 * Move a file, directory tree or wildcarded set of files.
2417 void WCMD_move (void)
2419 int status;
2420 WIN32_FIND_DATAW fd;
2421 HANDLE hff;
2422 WCHAR input[MAX_PATH];
2423 WCHAR output[MAX_PATH];
2424 WCHAR drive[10];
2425 WCHAR dir[MAX_PATH];
2426 WCHAR fname[MAX_PATH];
2427 WCHAR ext[MAX_PATH];
2429 if (param1[0] == 0x00) {
2430 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2431 return;
2434 /* If no destination supplied, assume current directory */
2435 if (param2[0] == 0x00) {
2436 strcpyW(param2, dotW);
2439 /* If 2nd parm is directory, then use original filename */
2440 /* Convert partial path to full path */
2441 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2442 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2443 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2444 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2446 /* Split into components */
2447 WCMD_splitpath(input, drive, dir, fname, ext);
2449 hff = FindFirstFileW(input, &fd);
2450 if (hff == INVALID_HANDLE_VALUE)
2451 return;
2453 do {
2454 WCHAR dest[MAX_PATH];
2455 WCHAR src[MAX_PATH];
2456 DWORD attribs;
2457 BOOL ok = TRUE;
2459 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2461 /* Build src & dest name */
2462 strcpyW(src, drive);
2463 strcatW(src, dir);
2465 /* See if dest is an existing directory */
2466 attribs = GetFileAttributesW(output);
2467 if (attribs != INVALID_FILE_ATTRIBUTES &&
2468 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2469 strcpyW(dest, output);
2470 strcatW(dest, slashW);
2471 strcatW(dest, fd.cFileName);
2472 } else {
2473 strcpyW(dest, output);
2476 strcatW(src, fd.cFileName);
2478 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2479 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2481 /* If destination exists, prompt unless /Y supplied */
2482 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2483 BOOL force = FALSE;
2484 WCHAR copycmd[MAXSTRING];
2485 DWORD len;
2487 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2488 if (strstrW (quals, parmNoY))
2489 force = FALSE;
2490 else if (strstrW (quals, parmY))
2491 force = TRUE;
2492 else {
2493 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2494 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2495 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2496 && ! lstrcmpiW (copycmd, parmY));
2499 /* Prompt if overwriting */
2500 if (!force) {
2501 WCHAR* question;
2503 /* Ask for confirmation */
2504 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2505 ok = WCMD_ask_confirm(question, FALSE, NULL);
2506 LocalFree(question);
2508 /* So delete the destination prior to the move */
2509 if (ok) {
2510 if (!DeleteFileW(dest)) {
2511 WCMD_print_error ();
2512 errorlevel = 1;
2513 ok = FALSE;
2519 if (ok) {
2520 status = MoveFileW(src, dest);
2521 } else {
2522 status = 1; /* Anything other than 0 to prevent error msg below */
2525 if (!status) {
2526 WCMD_print_error ();
2527 errorlevel = 1;
2529 } while (FindNextFileW(hff, &fd) != 0);
2531 FindClose(hff);
2534 /****************************************************************************
2535 * WCMD_pause
2537 * Suspend execution of a batch script until a key is typed
2540 void WCMD_pause (void)
2542 DWORD oldmode;
2543 BOOL have_console;
2544 DWORD count;
2545 WCHAR key;
2546 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2548 have_console = GetConsoleMode(hIn, &oldmode);
2549 if (have_console)
2550 SetConsoleMode(hIn, 0);
2552 WCMD_output_asis(anykey);
2553 WCMD_ReadFile(hIn, &key, 1, &count);
2554 if (have_console)
2555 SetConsoleMode(hIn, oldmode);
2558 /****************************************************************************
2559 * WCMD_remove_dir
2561 * Delete a directory.
2564 void WCMD_remove_dir (WCHAR *args) {
2566 int argno = 0;
2567 int argsProcessed = 0;
2568 WCHAR *argN = args;
2569 static const WCHAR parmS[] = {'/','S','\0'};
2570 static const WCHAR parmQ[] = {'/','Q','\0'};
2572 /* Loop through all args */
2573 while (argN) {
2574 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
2575 if (argN && argN[0] != '/') {
2576 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
2577 wine_dbgstr_w(quals));
2578 argsProcessed++;
2580 /* If subdirectory search not supplied, just try to remove
2581 and report error if it fails (eg if it contains a file) */
2582 if (strstrW (quals, parmS) == NULL) {
2583 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
2585 /* Otherwise use ShFileOp to recursively remove a directory */
2586 } else {
2588 SHFILEOPSTRUCTW lpDir;
2590 /* Ask first */
2591 if (strstrW (quals, parmQ) == NULL) {
2592 BOOL ok;
2593 WCHAR question[MAXSTRING];
2594 static const WCHAR fmt[] = {'%','s',' ','\0'};
2596 /* Ask for confirmation */
2597 wsprintfW(question, fmt, thisArg);
2598 ok = WCMD_ask_confirm(question, TRUE, NULL);
2600 /* Abort if answer is 'N' */
2601 if (!ok) return;
2604 /* Do the delete */
2605 lpDir.hwnd = NULL;
2606 lpDir.pTo = NULL;
2607 lpDir.pFrom = thisArg;
2608 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
2609 lpDir.wFunc = FO_DELETE;
2611 /* SHFileOperationW needs file list with a double null termination */
2612 thisArg[lstrlenW(thisArg) + 1] = 0x00;
2614 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
2619 /* Handle no valid args */
2620 if (argsProcessed == 0) {
2621 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2622 return;
2627 /****************************************************************************
2628 * WCMD_rename
2630 * Rename a file.
2633 void WCMD_rename (void)
2635 int status;
2636 HANDLE hff;
2637 WIN32_FIND_DATAW fd;
2638 WCHAR input[MAX_PATH];
2639 WCHAR *dotDst = NULL;
2640 WCHAR drive[10];
2641 WCHAR dir[MAX_PATH];
2642 WCHAR fname[MAX_PATH];
2643 WCHAR ext[MAX_PATH];
2645 errorlevel = 0;
2647 /* Must be at least two args */
2648 if (param1[0] == 0x00 || param2[0] == 0x00) {
2649 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2650 errorlevel = 1;
2651 return;
2654 /* Destination cannot contain a drive letter or directory separator */
2655 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
2656 SetLastError(ERROR_INVALID_PARAMETER);
2657 WCMD_print_error();
2658 errorlevel = 1;
2659 return;
2662 /* Convert partial path to full path */
2663 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2664 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2665 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
2666 dotDst = strchrW(param2, '.');
2668 /* Split into components */
2669 WCMD_splitpath(input, drive, dir, fname, ext);
2671 hff = FindFirstFileW(input, &fd);
2672 if (hff == INVALID_HANDLE_VALUE)
2673 return;
2675 do {
2676 WCHAR dest[MAX_PATH];
2677 WCHAR src[MAX_PATH];
2678 WCHAR *dotSrc = NULL;
2679 int dirLen;
2681 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2683 /* FIXME: If dest name or extension is *, replace with filename/ext
2684 part otherwise use supplied name. This supports:
2685 ren *.fred *.jim
2686 ren jim.* fred.* etc
2687 However, windows has a more complex algorithm supporting eg
2688 ?'s and *'s mid name */
2689 dotSrc = strchrW(fd.cFileName, '.');
2691 /* Build src & dest name */
2692 strcpyW(src, drive);
2693 strcatW(src, dir);
2694 strcpyW(dest, src);
2695 dirLen = strlenW(src);
2696 strcatW(src, fd.cFileName);
2698 /* Build name */
2699 if (param2[0] == '*') {
2700 strcatW(dest, fd.cFileName);
2701 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
2702 } else {
2703 strcatW(dest, param2);
2704 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
2707 /* Build Extension */
2708 if (dotDst && (*(dotDst+1)=='*')) {
2709 if (dotSrc) strcatW(dest, dotSrc);
2710 } else if (dotDst) {
2711 if (dotDst) strcatW(dest, dotDst);
2714 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2715 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2717 status = MoveFileW(src, dest);
2719 if (!status) {
2720 WCMD_print_error ();
2721 errorlevel = 1;
2723 } while (FindNextFileW(hff, &fd) != 0);
2725 FindClose(hff);
2728 /*****************************************************************************
2729 * WCMD_dupenv
2731 * Make a copy of the environment.
2733 static WCHAR *WCMD_dupenv( const WCHAR *env )
2735 WCHAR *env_copy;
2736 int len;
2738 if( !env )
2739 return NULL;
2741 len = 0;
2742 while ( env[len] )
2743 len += (strlenW(&env[len]) + 1);
2745 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
2746 if (!env_copy)
2748 WINE_ERR("out of memory\n");
2749 return env_copy;
2751 memcpy (env_copy, env, len*sizeof (WCHAR));
2752 env_copy[len] = 0;
2754 return env_copy;
2757 /*****************************************************************************
2758 * WCMD_setlocal
2760 * setlocal pushes the environment onto a stack
2761 * Save the environment as unicode so we don't screw anything up.
2763 void WCMD_setlocal (const WCHAR *s) {
2764 WCHAR *env;
2765 struct env_stack *env_copy;
2766 WCHAR cwd[MAX_PATH];
2768 /* setlocal does nothing outside of batch programs */
2769 if (!context) return;
2771 /* DISABLEEXTENSIONS ignored */
2773 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2774 if( !env_copy )
2776 WINE_ERR ("out of memory\n");
2777 return;
2780 env = GetEnvironmentStringsW ();
2781 env_copy->strings = WCMD_dupenv (env);
2782 if (env_copy->strings)
2784 env_copy->batchhandle = context->h;
2785 env_copy->next = saved_environment;
2786 saved_environment = env_copy;
2788 /* Save the current drive letter */
2789 GetCurrentDirectoryW(MAX_PATH, cwd);
2790 env_copy->u.cwd = cwd[0];
2792 else
2793 LocalFree (env_copy);
2795 FreeEnvironmentStringsW (env);
2799 /*****************************************************************************
2800 * WCMD_endlocal
2802 * endlocal pops the environment off a stack
2803 * Note: When searching for '=', search from WCHAR position 1, to handle
2804 * special internal environment variables =C:, =D: etc
2806 void WCMD_endlocal (void) {
2807 WCHAR *env, *old, *p;
2808 struct env_stack *temp;
2809 int len, n;
2811 /* setlocal does nothing outside of batch programs */
2812 if (!context) return;
2814 /* setlocal needs a saved environment from within the same context (batch
2815 program) as it was saved in */
2816 if (!saved_environment || saved_environment->batchhandle != context->h)
2817 return;
2819 /* pop the old environment from the stack */
2820 temp = saved_environment;
2821 saved_environment = temp->next;
2823 /* delete the current environment, totally */
2824 env = GetEnvironmentStringsW ();
2825 old = WCMD_dupenv (GetEnvironmentStringsW ());
2826 len = 0;
2827 while (old[len]) {
2828 n = strlenW(&old[len]) + 1;
2829 p = strchrW(&old[len] + 1, '=');
2830 if (p)
2832 *p++ = 0;
2833 SetEnvironmentVariableW (&old[len], NULL);
2835 len += n;
2837 LocalFree (old);
2838 FreeEnvironmentStringsW (env);
2840 /* restore old environment */
2841 env = temp->strings;
2842 len = 0;
2843 while (env[len]) {
2844 n = strlenW(&env[len]) + 1;
2845 p = strchrW(&env[len] + 1, '=');
2846 if (p)
2848 *p++ = 0;
2849 SetEnvironmentVariableW (&env[len], p);
2851 len += n;
2854 /* Restore current drive letter */
2855 if (IsCharAlphaW(temp->u.cwd)) {
2856 WCHAR envvar[4];
2857 WCHAR cwd[MAX_PATH];
2858 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2860 wsprintfW(envvar, fmt, temp->u.cwd);
2861 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2862 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2863 SetCurrentDirectoryW(cwd);
2867 LocalFree (env);
2868 LocalFree (temp);
2871 /*****************************************************************************
2872 * WCMD_setshow_default
2874 * Set/Show the current default directory
2877 void WCMD_setshow_default (const WCHAR *args) {
2879 BOOL status;
2880 WCHAR string[1024];
2881 WCHAR cwd[1024];
2882 WCHAR *pos;
2883 WIN32_FIND_DATAW fd;
2884 HANDLE hff;
2885 static const WCHAR parmD[] = {'/','D','\0'};
2887 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
2889 /* Skip /D and trailing whitespace if on the front of the command line */
2890 if (CompareStringW(LOCALE_USER_DEFAULT,
2891 NORM_IGNORECASE | SORT_STRINGSORT,
2892 args, 2, parmD, -1) == CSTR_EQUAL) {
2893 args += 2;
2894 while (*args && (*args==' ' || *args=='\t'))
2895 args++;
2898 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2899 if (strlenW(args) == 0) {
2900 strcatW (cwd, newlineW);
2901 WCMD_output_asis (cwd);
2903 else {
2904 /* Remove any double quotes, which may be in the
2905 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2906 pos = string;
2907 while (*args) {
2908 if (*args != '"') *pos++ = *args;
2909 args++;
2911 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2912 pos--;
2913 *pos = 0x00;
2915 /* Search for appropriate directory */
2916 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2917 hff = FindFirstFileW(string, &fd);
2918 if (hff != INVALID_HANDLE_VALUE) {
2919 do {
2920 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2921 WCHAR fpath[MAX_PATH];
2922 WCHAR drive[10];
2923 WCHAR dir[MAX_PATH];
2924 WCHAR fname[MAX_PATH];
2925 WCHAR ext[MAX_PATH];
2926 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2928 /* Convert path into actual directory spec */
2929 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2930 WCMD_splitpath(fpath, drive, dir, fname, ext);
2932 /* Rebuild path */
2933 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2934 break;
2936 } while (FindNextFileW(hff, &fd) != 0);
2937 FindClose(hff);
2940 /* Change to that directory */
2941 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2943 status = SetCurrentDirectoryW(string);
2944 if (!status) {
2945 errorlevel = 1;
2946 WCMD_print_error ();
2947 return;
2948 } else {
2950 /* Save away the actual new directory, to store as current location */
2951 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2953 /* Restore old directory if drive letter would change, and
2954 CD x:\directory /D (or pushd c:\directory) not supplied */
2955 if ((strstrW(quals, parmD) == NULL) &&
2956 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2957 SetCurrentDirectoryW(cwd);
2961 /* Set special =C: type environment variable, for drive letter of
2962 change of directory, even if path was restored due to missing
2963 /D (allows changing drive letter when not resident on that
2964 drive */
2965 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2966 WCHAR env[4];
2967 strcpyW(env, equalW);
2968 memcpy(env+1, string, 2 * sizeof(WCHAR));
2969 env[3] = 0x00;
2970 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2971 SetEnvironmentVariableW(env, string);
2975 return;
2978 /****************************************************************************
2979 * WCMD_setshow_date
2981 * Set/Show the system date
2982 * FIXME: Can't change date yet
2985 void WCMD_setshow_date (void) {
2987 WCHAR curdate[64], buffer[64];
2988 DWORD count;
2989 static const WCHAR parmT[] = {'/','T','\0'};
2991 if (strlenW(param1) == 0) {
2992 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2993 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2994 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2995 if (strstrW (quals, parmT) == NULL) {
2996 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2997 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2998 if (count > 2) {
2999 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3003 else WCMD_print_error ();
3005 else {
3006 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3010 /****************************************************************************
3011 * WCMD_compare
3012 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3013 * the equals sign.
3015 static int WCMD_compare( const void *a, const void *b )
3017 int r;
3018 const WCHAR * const *str_a = a, * const *str_b = b;
3019 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3020 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3021 if( r == CSTR_LESS_THAN ) return -1;
3022 if( r == CSTR_GREATER_THAN ) return 1;
3023 return 0;
3026 /****************************************************************************
3027 * WCMD_setshow_sortenv
3029 * sort variables into order for display
3030 * Optionally only display those who start with a stub
3031 * returns the count displayed
3033 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3035 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3036 const WCHAR **str;
3038 if (stub) stublen = strlenW(stub);
3040 /* count the number of strings, and the total length */
3041 while ( s[len] ) {
3042 len += (strlenW(&s[len]) + 1);
3043 count++;
3046 /* add the strings to an array */
3047 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3048 if( !str )
3049 return 0;
3050 str[0] = s;
3051 for( i=1; i<count; i++ )
3052 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3054 /* sort the array */
3055 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3057 /* print it */
3058 for( i=0; i<count; i++ ) {
3059 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3060 NORM_IGNORECASE | SORT_STRINGSORT,
3061 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3062 /* Don't display special internal variables */
3063 if (str[i][0] != '=') {
3064 WCMD_output_asis(str[i]);
3065 WCMD_output_asis(newlineW);
3066 displayedcount++;
3071 LocalFree( str );
3072 return displayedcount;
3075 /****************************************************************************
3076 * WCMD_setshow_env
3078 * Set/Show the environment variables
3081 void WCMD_setshow_env (WCHAR *s) {
3083 LPVOID env;
3084 WCHAR *p;
3085 int status;
3086 static const WCHAR parmP[] = {'/','P','\0'};
3088 if (param1[0] == 0x00 && quals[0] == 0x00) {
3089 env = GetEnvironmentStringsW();
3090 WCMD_setshow_sortenv( env, NULL );
3091 return;
3094 /* See if /P supplied, and if so echo the prompt, and read in a reply */
3095 if (CompareStringW(LOCALE_USER_DEFAULT,
3096 NORM_IGNORECASE | SORT_STRINGSORT,
3097 s, 2, parmP, -1) == CSTR_EQUAL) {
3098 WCHAR string[MAXSTRING];
3099 DWORD count;
3101 s += 2;
3102 while (*s && (*s==' ' || *s=='\t')) s++;
3103 if (*s=='\"')
3104 WCMD_strip_quotes(s);
3106 /* If no parameter, or no '=' sign, return an error */
3107 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
3108 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3109 return;
3112 /* Output the prompt */
3113 *p++ = '\0';
3114 if (strlenW(p) != 0) WCMD_output_asis(p);
3116 /* Read the reply */
3117 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3118 if (count > 1) {
3119 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3120 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3121 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3122 wine_dbgstr_w(string));
3123 status = SetEnvironmentVariableW(s, string);
3126 } else {
3127 DWORD gle;
3129 if (*s=='\"')
3130 WCMD_strip_quotes(s);
3131 p = strchrW (s, '=');
3132 if (p == NULL) {
3133 env = GetEnvironmentStringsW();
3134 if (WCMD_setshow_sortenv( env, s ) == 0) {
3135 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
3136 errorlevel = 1;
3138 return;
3140 *p++ = '\0';
3142 if (strlenW(p) == 0) p = NULL;
3143 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
3144 wine_dbgstr_w(p));
3145 status = SetEnvironmentVariableW(s, p);
3146 gle = GetLastError();
3147 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
3148 errorlevel = 1;
3149 } else if ((!status)) WCMD_print_error();
3150 else errorlevel = 0;
3154 /****************************************************************************
3155 * WCMD_setshow_path
3157 * Set/Show the path environment variable
3160 void WCMD_setshow_path (const WCHAR *args) {
3162 WCHAR string[1024];
3163 DWORD status;
3164 static const WCHAR pathW[] = {'P','A','T','H','\0'};
3165 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
3167 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
3168 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
3169 if (status != 0) {
3170 WCMD_output_asis ( pathEqW);
3171 WCMD_output_asis ( string);
3172 WCMD_output_asis ( newlineW);
3174 else {
3175 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
3178 else {
3179 if (*args == '=') args++; /* Skip leading '=' */
3180 status = SetEnvironmentVariableW(pathW, args);
3181 if (!status) WCMD_print_error();
3185 /****************************************************************************
3186 * WCMD_setshow_prompt
3188 * Set or show the command prompt.
3191 void WCMD_setshow_prompt (void) {
3193 WCHAR *s;
3194 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
3196 if (strlenW(param1) == 0) {
3197 SetEnvironmentVariableW(promptW, NULL);
3199 else {
3200 s = param1;
3201 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
3202 if (strlenW(s) == 0) {
3203 SetEnvironmentVariableW(promptW, NULL);
3205 else SetEnvironmentVariableW(promptW, s);
3209 /****************************************************************************
3210 * WCMD_setshow_time
3212 * Set/Show the system time
3213 * FIXME: Can't change time yet
3216 void WCMD_setshow_time (void) {
3218 WCHAR curtime[64], buffer[64];
3219 DWORD count;
3220 SYSTEMTIME st;
3221 static const WCHAR parmT[] = {'/','T','\0'};
3223 if (strlenW(param1) == 0) {
3224 GetLocalTime(&st);
3225 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
3226 curtime, sizeof(curtime)/sizeof(WCHAR))) {
3227 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
3228 if (strstrW (quals, parmT) == NULL) {
3229 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
3230 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3231 if (count > 2) {
3232 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3236 else WCMD_print_error ();
3238 else {
3239 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3243 /****************************************************************************
3244 * WCMD_shift
3246 * Shift batch parameters.
3247 * Optional /n says where to start shifting (n=0-8)
3250 void WCMD_shift (const WCHAR *args) {
3251 int start;
3253 if (context != NULL) {
3254 WCHAR *pos = strchrW(args, '/');
3255 int i;
3257 if (pos == NULL) {
3258 start = 0;
3259 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
3260 start = (*(pos+1) - '0');
3261 } else {
3262 SetLastError(ERROR_INVALID_PARAMETER);
3263 WCMD_print_error();
3264 return;
3267 WINE_TRACE("Shifting variables, starting at %d\n", start);
3268 for (i=start;i<=8;i++) {
3269 context -> shift_count[i] = context -> shift_count[i+1] + 1;
3271 context -> shift_count[9] = context -> shift_count[9] + 1;
3276 /****************************************************************************
3277 * WCMD_start
3279 void WCMD_start(const WCHAR *args)
3281 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
3282 '\\','s','t','a','r','t','.','e','x','e',0};
3283 WCHAR file[MAX_PATH];
3284 WCHAR *cmdline;
3285 STARTUPINFOW st;
3286 PROCESS_INFORMATION pi;
3288 GetWindowsDirectoryW( file, MAX_PATH );
3289 strcatW( file, exeW );
3290 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(args) + 2) * sizeof(WCHAR) );
3291 strcpyW( cmdline, file );
3292 strcatW( cmdline, spaceW );
3293 strcatW( cmdline, args );
3295 memset( &st, 0, sizeof(STARTUPINFOW) );
3296 st.cb = sizeof(STARTUPINFOW);
3298 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
3300 WaitForSingleObject( pi.hProcess, INFINITE );
3301 GetExitCodeProcess( pi.hProcess, &errorlevel );
3302 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
3303 CloseHandle(pi.hProcess);
3304 CloseHandle(pi.hThread);
3306 else
3308 SetLastError(ERROR_FILE_NOT_FOUND);
3309 WCMD_print_error ();
3310 errorlevel = 9009;
3312 HeapFree( GetProcessHeap(), 0, cmdline );
3315 /****************************************************************************
3316 * WCMD_title
3318 * Set the console title
3320 void WCMD_title (const WCHAR *args) {
3321 SetConsoleTitleW(args);
3324 /****************************************************************************
3325 * WCMD_type
3327 * Copy a file to standard output.
3330 void WCMD_type (WCHAR *args) {
3332 int argno = 0;
3333 WCHAR *argN = args;
3334 BOOL writeHeaders = FALSE;
3336 if (param1[0] == 0x00) {
3337 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3338 return;
3341 if (param2[0] != 0x00) writeHeaders = TRUE;
3343 /* Loop through all args */
3344 errorlevel = 0;
3345 while (argN) {
3346 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3348 HANDLE h;
3349 WCHAR buffer[512];
3350 DWORD count;
3352 if (!argN) break;
3354 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3355 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3356 FILE_ATTRIBUTE_NORMAL, NULL);
3357 if (h == INVALID_HANDLE_VALUE) {
3358 WCMD_print_error ();
3359 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3360 errorlevel = 1;
3361 } else {
3362 if (writeHeaders) {
3363 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
3364 WCMD_output(fmt, thisArg);
3366 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
3367 if (count == 0) break; /* ReadFile reports success on EOF! */
3368 buffer[count] = 0;
3369 WCMD_output_asis (buffer);
3371 CloseHandle (h);
3376 /****************************************************************************
3377 * WCMD_more
3379 * Output either a file or stdin to screen in pages
3382 void WCMD_more (WCHAR *args) {
3384 int argno = 0;
3385 WCHAR *argN = args;
3386 WCHAR moreStr[100];
3387 WCHAR moreStrPage[100];
3388 WCHAR buffer[512];
3389 DWORD count;
3390 static const WCHAR moreStart[] = {'-','-',' ','\0'};
3391 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
3392 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
3393 ')',' ','-','-','\n','\0'};
3394 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
3396 /* Prefix the NLS more with '-- ', then load the text */
3397 errorlevel = 0;
3398 strcpyW(moreStr, moreStart);
3399 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
3400 (sizeof(moreStr)/sizeof(WCHAR))-3);
3402 if (param1[0] == 0x00) {
3404 /* Wine implements pipes via temporary files, and hence stdin is
3405 effectively reading from the file. This means the prompts for
3406 more are satisfied by the next line from the input (file). To
3407 avoid this, ensure stdin is to the console */
3408 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
3409 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
3410 FILE_SHARE_READ, NULL, OPEN_EXISTING,
3411 FILE_ATTRIBUTE_NORMAL, 0);
3412 WINE_TRACE("No parms - working probably in pipe mode\n");
3413 SetStdHandle(STD_INPUT_HANDLE, hConIn);
3415 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
3416 once you get in this bit unless due to a pipe, its going to end badly... */
3417 wsprintfW(moreStrPage, moreFmt, moreStr);
3419 WCMD_enter_paged_mode(moreStrPage);
3420 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3421 if (count == 0) break; /* ReadFile reports success on EOF! */
3422 buffer[count] = 0;
3423 WCMD_output_asis (buffer);
3425 WCMD_leave_paged_mode();
3427 /* Restore stdin to what it was */
3428 SetStdHandle(STD_INPUT_HANDLE, hstdin);
3429 CloseHandle(hConIn);
3431 return;
3432 } else {
3433 BOOL needsPause = FALSE;
3435 /* Loop through all args */
3436 WINE_TRACE("Parms supplied - working through each file\n");
3437 WCMD_enter_paged_mode(moreStrPage);
3439 while (argN) {
3440 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3441 HANDLE h;
3443 if (!argN) break;
3445 if (needsPause) {
3447 /* Wait */
3448 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
3449 WCMD_leave_paged_mode();
3450 WCMD_output_asis(moreStrPage);
3451 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3452 WCMD_enter_paged_mode(moreStrPage);
3456 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
3457 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
3458 FILE_ATTRIBUTE_NORMAL, NULL);
3459 if (h == INVALID_HANDLE_VALUE) {
3460 WCMD_print_error ();
3461 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
3462 errorlevel = 1;
3463 } else {
3464 ULONG64 curPos = 0;
3465 ULONG64 fileLen = 0;
3466 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
3468 /* Get the file size */
3469 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
3470 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
3472 needsPause = TRUE;
3473 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
3474 if (count == 0) break; /* ReadFile reports success on EOF! */
3475 buffer[count] = 0;
3476 curPos += count;
3478 /* Update % count (would be used in WCMD_output_asis as prompt) */
3479 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
3481 WCMD_output_asis (buffer);
3483 CloseHandle (h);
3487 WCMD_leave_paged_mode();
3491 /****************************************************************************
3492 * WCMD_verify
3494 * Display verify flag.
3495 * FIXME: We don't actually do anything with the verify flag other than toggle
3496 * it...
3499 void WCMD_verify (const WCHAR *args) {
3501 int count;
3503 count = strlenW(args);
3504 if (count == 0) {
3505 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
3506 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
3507 return;
3509 if (lstrcmpiW(args, onW) == 0) {
3510 verify_mode = TRUE;
3511 return;
3513 else if (lstrcmpiW(args, offW) == 0) {
3514 verify_mode = FALSE;
3515 return;
3517 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
3520 /****************************************************************************
3521 * WCMD_version
3523 * Display version info.
3526 void WCMD_version (void) {
3528 WCMD_output_asis (version_string);
3532 /****************************************************************************
3533 * WCMD_volume
3535 * Display volume information (set_label = FALSE)
3536 * Additionally set volume label (set_label = TRUE)
3537 * Returns 1 on success, 0 otherwise
3540 int WCMD_volume(BOOL set_label, const WCHAR *path)
3542 DWORD count, serial;
3543 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
3544 BOOL status;
3546 if (strlenW(path) == 0) {
3547 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
3548 if (!status) {
3549 WCMD_print_error ();
3550 return 0;
3552 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
3553 &serial, NULL, NULL, NULL, 0);
3555 else {
3556 static const WCHAR fmt[] = {'%','s','\\','\0'};
3557 if ((path[1] != ':') || (strlenW(path) != 2)) {
3558 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
3559 return 0;
3561 wsprintfW (curdir, fmt, path);
3562 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
3563 &serial, NULL,
3564 NULL, NULL, 0);
3566 if (!status) {
3567 WCMD_print_error ();
3568 return 0;
3570 if (label[0] != '\0') {
3571 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
3572 curdir[0], label);
3574 else {
3575 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
3576 curdir[0]);
3578 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
3579 HIWORD(serial), LOWORD(serial));
3580 if (set_label) {
3581 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
3582 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
3583 if (count > 1) {
3584 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
3585 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
3587 if (strlenW(path) != 0) {
3588 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
3590 else {
3591 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
3594 return 1;
3597 /**************************************************************************
3598 * WCMD_exit
3600 * Exit either the process, or just this batch program
3604 void WCMD_exit (CMD_LIST **cmdList) {
3606 static const WCHAR parmB[] = {'/','B','\0'};
3607 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
3609 if (context && lstrcmpiW(quals, parmB) == 0) {
3610 errorlevel = rc;
3611 context -> skip_rest = TRUE;
3612 *cmdList = NULL;
3613 } else {
3614 ExitProcess(rc);
3619 /*****************************************************************************
3620 * WCMD_assoc
3622 * Lists or sets file associations (assoc = TRUE)
3623 * Lists or sets file types (assoc = FALSE)
3625 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
3627 HKEY key;
3628 DWORD accessOptions = KEY_READ;
3629 WCHAR *newValue;
3630 LONG rc = ERROR_SUCCESS;
3631 WCHAR keyValue[MAXSTRING];
3632 DWORD valueLen = MAXSTRING;
3633 HKEY readKey;
3634 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
3635 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
3637 /* See if parameter includes '=' */
3638 errorlevel = 0;
3639 newValue = strchrW(args, '=');
3640 if (newValue) accessOptions |= KEY_WRITE;
3642 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
3643 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
3644 accessOptions, &key) != ERROR_SUCCESS) {
3645 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
3646 return;
3649 /* If no parameters then list all associations */
3650 if (*args == 0x00) {
3651 int index = 0;
3653 /* Enumerate all the keys */
3654 while (rc != ERROR_NO_MORE_ITEMS) {
3655 WCHAR keyName[MAXSTRING];
3656 DWORD nameLen;
3658 /* Find the next value */
3659 nameLen = MAXSTRING;
3660 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
3662 if (rc == ERROR_SUCCESS) {
3664 /* Only interested in extension ones if assoc, or others
3665 if not assoc */
3666 if ((keyName[0] == '.' && assoc) ||
3667 (!(keyName[0] == '.') && (!assoc)))
3669 WCHAR subkey[MAXSTRING];
3670 strcpyW(subkey, keyName);
3671 if (!assoc) strcatW(subkey, shOpCmdW);
3673 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3675 valueLen = sizeof(keyValue)/sizeof(WCHAR);
3676 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3677 WCMD_output_asis(keyName);
3678 WCMD_output_asis(equalW);
3679 /* If no default value found, leave line empty after '=' */
3680 if (rc == ERROR_SUCCESS) {
3681 WCMD_output_asis(keyValue);
3683 WCMD_output_asis(newlineW);
3684 RegCloseKey(readKey);
3690 } else {
3692 /* Parameter supplied - if no '=' on command line, its a query */
3693 if (newValue == NULL) {
3694 WCHAR *space;
3695 WCHAR subkey[MAXSTRING];
3697 /* Query terminates the parameter at the first space */
3698 strcpyW(keyValue, args);
3699 space = strchrW(keyValue, ' ');
3700 if (space) *space=0x00;
3702 /* Set up key name */
3703 strcpyW(subkey, keyValue);
3704 if (!assoc) strcatW(subkey, shOpCmdW);
3706 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
3708 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
3709 WCMD_output_asis(args);
3710 WCMD_output_asis(equalW);
3711 /* If no default value found, leave line empty after '=' */
3712 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
3713 WCMD_output_asis(newlineW);
3714 RegCloseKey(readKey);
3716 } else {
3717 WCHAR msgbuffer[MAXSTRING];
3719 /* Load the translated 'File association not found' */
3720 if (assoc) {
3721 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3722 } else {
3723 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
3725 WCMD_output_stderr(msgbuffer, keyValue);
3726 errorlevel = 2;
3729 /* Not a query - its a set or clear of a value */
3730 } else {
3732 WCHAR subkey[MAXSTRING];
3734 /* Get pointer to new value */
3735 *newValue = 0x00;
3736 newValue++;
3738 /* Set up key name */
3739 strcpyW(subkey, args);
3740 if (!assoc) strcatW(subkey, shOpCmdW);
3742 /* If nothing after '=' then clear value - only valid for ASSOC */
3743 if (*newValue == 0x00) {
3745 if (assoc) rc = RegDeleteKeyW(key, args);
3746 if (assoc && rc == ERROR_SUCCESS) {
3747 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
3749 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
3750 WCMD_print_error();
3751 errorlevel = 2;
3753 } else {
3754 WCHAR msgbuffer[MAXSTRING];
3756 /* Load the translated 'File association not found' */
3757 if (assoc) {
3758 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
3759 sizeof(msgbuffer)/sizeof(WCHAR));
3760 } else {
3761 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
3762 sizeof(msgbuffer)/sizeof(WCHAR));
3764 WCMD_output_stderr(msgbuffer, keyValue);
3765 errorlevel = 2;
3768 /* It really is a set value = contents */
3769 } else {
3770 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
3771 accessOptions, NULL, &readKey, NULL);
3772 if (rc == ERROR_SUCCESS) {
3773 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
3774 (LPBYTE)newValue,
3775 sizeof(WCHAR) * (strlenW(newValue) + 1));
3776 RegCloseKey(readKey);
3779 if (rc != ERROR_SUCCESS) {
3780 WCMD_print_error();
3781 errorlevel = 2;
3782 } else {
3783 WCMD_output_asis(args);
3784 WCMD_output_asis(equalW);
3785 WCMD_output_asis(newValue);
3786 WCMD_output_asis(newlineW);
3792 /* Clean up */
3793 RegCloseKey(key);
3796 /****************************************************************************
3797 * WCMD_color
3799 * Colors the terminal screen.
3802 void WCMD_color (void) {
3804 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3805 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3807 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3808 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3809 return;
3812 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3814 COORD topLeft;
3815 DWORD screenSize;
3816 DWORD color = 0;
3818 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3820 topLeft.X = 0;
3821 topLeft.Y = 0;
3823 /* Convert the color hex digits */
3824 if (param1[0] == 0x00) {
3825 color = defaultColor;
3826 } else {
3827 color = strtoulW(param1, NULL, 16);
3830 /* Fail if fg == bg color */
3831 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3832 errorlevel = 1;
3833 return;
3836 /* Set the current screen contents and ensure all future writes
3837 remain this color */
3838 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3839 SetConsoleTextAttribute(hStdOut, color);