winedbg/gdbproxy: Use the WINEDEBUG interface for debugging winedbg.
[wine.git] / programs / cmd / builtins.c
blob088632f214eacafe8e1e6add65436a42388a5ed4
1 /*
2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * FIXME:
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
31 #include "wcmd.h"
32 #include <shellapi.h>
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
37 extern int defaultColor;
38 extern BOOL echo_mode;
39 extern BOOL interactive;
41 struct env_stack *pushd_directories;
42 const WCHAR dotW[] = {'.','\0'};
43 const WCHAR dotdotW[] = {'.','.','\0'};
44 const WCHAR nullW[] = {'\0'};
45 const WCHAR starW[] = {'*','\0'};
46 const WCHAR slashW[] = {'\\','\0'};
47 const WCHAR equalW[] = {'=','\0'};
48 const WCHAR wildcardsW[] = {'*','?','\0'};
49 const WCHAR slashstarW[] = {'\\','*','\0'};
50 const WCHAR deviceW[] = {'\\','\\','.','\\','\0'};
51 const WCHAR inbuilt[][10] = {
52 {'C','A','L','L','\0'},
53 {'C','D','\0'},
54 {'C','H','D','I','R','\0'},
55 {'C','L','S','\0'},
56 {'C','O','P','Y','\0'},
57 {'C','T','T','Y','\0'},
58 {'D','A','T','E','\0'},
59 {'D','E','L','\0'},
60 {'D','I','R','\0'},
61 {'E','C','H','O','\0'},
62 {'E','R','A','S','E','\0'},
63 {'F','O','R','\0'},
64 {'G','O','T','O','\0'},
65 {'H','E','L','P','\0'},
66 {'I','F','\0'},
67 {'L','A','B','E','L','\0'},
68 {'M','D','\0'},
69 {'M','K','D','I','R','\0'},
70 {'M','O','V','E','\0'},
71 {'P','A','T','H','\0'},
72 {'P','A','U','S','E','\0'},
73 {'P','R','O','M','P','T','\0'},
74 {'R','E','M','\0'},
75 {'R','E','N','\0'},
76 {'R','E','N','A','M','E','\0'},
77 {'R','D','\0'},
78 {'R','M','D','I','R','\0'},
79 {'S','E','T','\0'},
80 {'S','H','I','F','T','\0'},
81 {'S','T','A','R','T','\0'},
82 {'T','I','M','E','\0'},
83 {'T','I','T','L','E','\0'},
84 {'T','Y','P','E','\0'},
85 {'V','E','R','I','F','Y','\0'},
86 {'V','E','R','\0'},
87 {'V','O','L','\0'},
88 {'E','N','D','L','O','C','A','L','\0'},
89 {'S','E','T','L','O','C','A','L','\0'},
90 {'P','U','S','H','D','\0'},
91 {'P','O','P','D','\0'},
92 {'A','S','S','O','C','\0'},
93 {'C','O','L','O','R','\0'},
94 {'F','T','Y','P','E','\0'},
95 {'M','O','R','E','\0'},
96 {'C','H','O','I','C','E','\0'},
97 {'M','K','L','I','N','K','\0'},
98 {'E','X','I','T','\0'}
100 static const WCHAR externals[][10] = {
101 {'A','T','T','R','I','B','\0'},
102 {'X','C','O','P','Y','\0'}
104 static const WCHAR onW[] = {'O','N','\0'};
105 static const WCHAR offW[] = {'O','F','F','\0'};
106 static const WCHAR parmY[] = {'/','Y','\0'};
107 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
108 static const WCHAR eqeqW[] = {'=','=','\0'};
110 static HINSTANCE hinst;
111 struct env_stack *saved_environment;
112 static BOOL verify_mode = FALSE;
114 /* set /a routines work from single character operators, but some of the
115 operators are multiple character ones, especially the assignment ones.
116 Temporarily represent these using the values below on the operator stack */
117 #define OP_POSITIVE 'P'
118 #define OP_NEGATIVE 'N'
119 #define OP_ASSSIGNMUL 'a'
120 #define OP_ASSSIGNDIV 'b'
121 #define OP_ASSSIGNMOD 'c'
122 #define OP_ASSSIGNADD 'd'
123 #define OP_ASSSIGNSUB 'e'
124 #define OP_ASSSIGNAND 'f'
125 #define OP_ASSSIGNNOT 'g'
126 #define OP_ASSSIGNOR 'h'
127 #define OP_ASSSIGNSHL 'i'
128 #define OP_ASSSIGNSHR 'j'
130 /* This maintains a stack of operators, holding both the operator precedence
131 and the single character representation of the operator in question */
132 typedef struct _OPSTACK
134 int precedence;
135 WCHAR op;
136 struct _OPSTACK *next;
137 } OPSTACK;
139 /* This maintains a stack of values, where each value can either be a
140 numeric value, or a string representing an environment variable */
141 typedef struct _VARSTACK
143 BOOL isnum;
144 WCHAR *variable;
145 int value;
146 struct _VARSTACK *next;
147 } VARSTACK;
149 /* This maintains a mapping between the calculated operator and the
150 single character representation for the assignment operators. */
151 static struct
153 WCHAR op;
154 WCHAR calculatedop;
155 } calcassignments[] =
157 {'*', OP_ASSSIGNMUL},
158 {'/', OP_ASSSIGNDIV},
159 {'%', OP_ASSSIGNMOD},
160 {'+', OP_ASSSIGNADD},
161 {'-', OP_ASSSIGNSUB},
162 {'&', OP_ASSSIGNAND},
163 {'^', OP_ASSSIGNNOT},
164 {'|', OP_ASSSIGNOR},
165 {'<', OP_ASSSIGNSHL},
166 {'>', OP_ASSSIGNSHR},
167 {' ',' '}
170 /**************************************************************************
171 * WCMD_ask_confirm
173 * Issue a message and ask for confirmation, waiting on a valid answer.
175 * Returns True if Y (or A) answer is selected
176 * If optionAll contains a pointer, ALL is allowed, and if answered
177 * set to TRUE
180 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
181 BOOL *optionAll) {
183 UINT msgid;
184 WCHAR confirm[MAXSTRING];
185 WCHAR options[MAXSTRING];
186 WCHAR Ybuffer[MAXSTRING];
187 WCHAR Nbuffer[MAXSTRING];
188 WCHAR Abuffer[MAXSTRING];
189 WCHAR answer[MAX_PATH] = {'\0'};
190 DWORD count = 0;
192 /* Load the translated valid answers */
193 if (showSureText)
194 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
195 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
196 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
197 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
198 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
199 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
201 /* Loop waiting on a valid answer */
202 if (optionAll)
203 *optionAll = FALSE;
204 while (1)
206 WCMD_output_asis (message);
207 if (showSureText)
208 WCMD_output_asis (confirm);
209 WCMD_output_asis (options);
210 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
211 answer[0] = toupperW(answer[0]);
212 if (answer[0] == Ybuffer[0])
213 return TRUE;
214 if (answer[0] == Nbuffer[0])
215 return FALSE;
216 if (optionAll && answer[0] == Abuffer[0])
218 *optionAll = TRUE;
219 return TRUE;
224 /****************************************************************************
225 * WCMD_clear_screen
227 * Clear the terminal screen.
230 void WCMD_clear_screen (void) {
232 /* Emulate by filling the screen from the top left to bottom right with
233 spaces, then moving the cursor to the top left afterwards */
234 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
235 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
237 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
239 COORD topLeft;
240 DWORD screenSize, written;
242 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
244 topLeft.X = 0;
245 topLeft.Y = 0;
246 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
247 FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
248 SetConsoleCursorPosition(hStdOut, topLeft);
252 /****************************************************************************
253 * WCMD_change_tty
255 * Change the default i/o device (ie redirect STDin/STDout).
258 void WCMD_change_tty (void) {
260 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
264 /****************************************************************************
265 * WCMD_choice
269 void WCMD_choice (const WCHAR * args) {
271 static const WCHAR bellW[] = {7,0};
272 static const WCHAR commaW[] = {',',0};
273 static const WCHAR bracket_open[] = {'[',0};
274 static const WCHAR bracket_close[] = {']','?',0};
275 WCHAR answer[16];
276 WCHAR buffer[16];
277 WCHAR *ptr = NULL;
278 WCHAR *opt_c = NULL;
279 WCHAR *my_command = NULL;
280 WCHAR opt_default = 0;
281 DWORD opt_timeout = 0;
282 DWORD count;
283 DWORD oldmode;
284 BOOL have_console;
285 BOOL opt_n = FALSE;
286 BOOL opt_s = FALSE;
288 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
289 errorlevel = 0;
291 my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
293 ptr = WCMD_skip_leading_spaces(my_command);
294 while (*ptr == '/') {
295 switch (toupperW(ptr[1])) {
296 case 'C':
297 ptr += 2;
298 /* the colon is optional */
299 if (*ptr == ':')
300 ptr++;
302 if (!*ptr || isspaceW(*ptr)) {
303 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
304 heap_free(my_command);
305 return;
308 /* remember the allowed keys (overwrite previous /C option) */
309 opt_c = ptr;
310 while (*ptr && (!isspaceW(*ptr)))
311 ptr++;
313 if (*ptr) {
314 /* terminate allowed chars */
315 *ptr = 0;
316 ptr = WCMD_skip_leading_spaces(&ptr[1]);
318 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
319 break;
321 case 'N':
322 opt_n = TRUE;
323 ptr = WCMD_skip_leading_spaces(&ptr[2]);
324 break;
326 case 'S':
327 opt_s = TRUE;
328 ptr = WCMD_skip_leading_spaces(&ptr[2]);
329 break;
331 case 'T':
332 ptr = &ptr[2];
333 /* the colon is optional */
334 if (*ptr == ':')
335 ptr++;
337 opt_default = *ptr++;
339 if (!opt_default || (*ptr != ',')) {
340 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
341 heap_free(my_command);
342 return;
344 ptr++;
346 count = 0;
347 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
348 count++;
349 ptr++;
352 answer[count] = 0;
353 opt_timeout = atoiW(answer);
355 ptr = WCMD_skip_leading_spaces(ptr);
356 break;
358 default:
359 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
360 heap_free(my_command);
361 return;
365 if (opt_timeout)
366 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
368 if (have_console)
369 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
371 /* use default keys, when needed: localized versions of "Y"es and "No" */
372 if (!opt_c) {
373 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
374 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
375 opt_c = buffer;
376 buffer[2] = 0;
379 /* print the question, when needed */
380 if (*ptr)
381 WCMD_output_asis(ptr);
383 if (!opt_s) {
384 struprW(opt_c);
385 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
388 if (!opt_n) {
389 /* print a list of all allowed answers inside brackets */
390 WCMD_output_asis(bracket_open);
391 ptr = opt_c;
392 answer[1] = 0;
393 while ((answer[0] = *ptr++)) {
394 WCMD_output_asis(answer);
395 if (*ptr)
396 WCMD_output_asis(commaW);
398 WCMD_output_asis(bracket_close);
401 while (TRUE) {
403 /* FIXME: Add support for option /T */
404 answer[1] = 0; /* terminate single character string */
405 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
407 if (!opt_s)
408 answer[0] = toupperW(answer[0]);
410 ptr = strchrW(opt_c, answer[0]);
411 if (ptr) {
412 WCMD_output_asis(answer);
413 WCMD_output_asis(newlineW);
414 if (have_console)
415 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
417 errorlevel = (ptr - opt_c) + 1;
418 WINE_TRACE("answer: %d\n", errorlevel);
419 heap_free(my_command);
420 return;
422 else
424 /* key not allowed: play the bell */
425 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
426 WCMD_output_asis(bellW);
431 /****************************************************************************
432 * WCMD_AppendEOF
434 * Adds an EOF onto the end of a file
435 * Returns TRUE on success
437 static BOOL WCMD_AppendEOF(WCHAR *filename)
439 HANDLE h;
440 DWORD bytes_written;
442 char eof = '\x1a';
444 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
445 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
446 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
448 if (h == INVALID_HANDLE_VALUE) {
449 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
450 return FALSE;
451 } else {
452 SetFilePointer (h, 0, NULL, FILE_END);
453 if (!WriteFile(h, &eof, 1, &bytes_written, NULL)) {
454 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
455 CloseHandle(h);
456 return FALSE;
458 CloseHandle(h);
460 return TRUE;
463 /****************************************************************************
464 * WCMD_IsSameFile
466 * Checks if the two paths reference to the same file
468 static BOOL WCMD_IsSameFile(const WCHAR *name1, const WCHAR *name2)
470 BOOL ret = FALSE;
471 HANDLE file1 = INVALID_HANDLE_VALUE, file2 = INVALID_HANDLE_VALUE;
472 BY_HANDLE_FILE_INFORMATION info1, info2;
474 file1 = CreateFileW(name1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
475 if (file1 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file1, &info1))
476 goto end;
478 file2 = CreateFileW(name2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
479 if (file2 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file2, &info2))
480 goto end;
482 ret = info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
483 && info1.nFileIndexHigh == info2.nFileIndexHigh
484 && info1.nFileIndexLow == info2.nFileIndexLow;
485 end:
486 if (file1 != INVALID_HANDLE_VALUE)
487 CloseHandle(file1);
488 if (file2 != INVALID_HANDLE_VALUE)
489 CloseHandle(file2);
490 return ret;
493 /****************************************************************************
494 * WCMD_ManualCopy
496 * Copies from a file
497 * optionally reading only until EOF (ascii copy)
498 * optionally appending onto an existing file (append)
499 * Returns TRUE on success
501 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
503 HANDLE in,out;
504 BOOL ok;
505 DWORD bytesread, byteswritten;
507 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
508 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
510 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
511 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
512 if (in == INVALID_HANDLE_VALUE) {
513 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
514 return FALSE;
517 /* Open the output file, overwriting if not appending */
518 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
519 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
520 if (out == INVALID_HANDLE_VALUE) {
521 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
522 CloseHandle(in);
523 return FALSE;
526 /* Move to end of destination if we are going to append to it */
527 if (append) {
528 SetFilePointer(out, 0, NULL, FILE_END);
531 /* Loop copying data from source to destination until EOF read */
534 char buffer[MAXSTRING];
536 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
537 if (ok) {
539 /* Stop at first EOF */
540 if (ascii) {
541 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
542 if (ptr) bytesread = (ptr - buffer);
545 if (bytesread) {
546 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
547 if (!ok || byteswritten != bytesread) {
548 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
549 wine_dbgstr_w(dstname), GetLastError());
552 } else {
553 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
554 wine_dbgstr_w(srcname), GetLastError());
556 } while (ok && bytesread > 0);
558 CloseHandle(out);
559 CloseHandle(in);
560 return ok;
563 /****************************************************************************
564 * WCMD_copy
566 * Copy a file or wildcarded set.
567 * For ascii/binary type copies, it gets complex:
568 * Syntax on command line is
569 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
570 * Where first /a or /b sets 'mode in operation' until another is found
571 * once another is found, it applies to the file preceding the /a or /b
572 * In addition each filename can contain wildcards
573 * To make matters worse, the + may be in the same parameter (i.e. no
574 * whitespace) or with whitespace separating it
576 * ASCII mode on read == read and stop at first EOF
577 * ASCII mode on write == append EOF to destination
578 * Binary == copy as-is
580 * Design of this is to build up a list of files which will be copied into a
581 * list, then work through the list file by file.
582 * If no destination is specified, it defaults to the name of the first file in
583 * the list, but the current directory.
587 void WCMD_copy(WCHAR * args) {
589 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
590 WCHAR *thisparam;
591 int argno = 0;
592 WCHAR *rawarg;
593 WIN32_FIND_DATAW fd;
594 HANDLE hff = INVALID_HANDLE_VALUE;
595 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
596 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
597 BOOL anyconcats = FALSE; /* Have we found any + options */
598 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
599 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
600 BOOL prompt; /* Prompt before overwriting */
601 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
602 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
603 BOOL status;
604 WCHAR copycmd[4];
605 DWORD len;
606 BOOL dstisdevice = FALSE;
607 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
609 typedef struct _COPY_FILES
611 struct _COPY_FILES *next;
612 BOOL concatenate;
613 WCHAR *name;
614 int binarycopy;
615 } COPY_FILES;
616 COPY_FILES *sourcelist = NULL;
617 COPY_FILES *lastcopyentry = NULL;
618 COPY_FILES *destination = NULL;
619 COPY_FILES *thiscopy = NULL;
620 COPY_FILES *prevcopy = NULL;
622 /* Assume we were successful! */
623 errorlevel = 0;
625 /* If no args supplied at all, report an error */
626 if (param1[0] == 0x00) {
627 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
628 errorlevel = 1;
629 return;
632 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
634 /* Walk through all args, building up a list of files to process */
635 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
636 while (*(thisparam)) {
637 WCHAR *pos1, *pos2;
638 BOOL inquotes;
640 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
642 /* Handle switches */
643 if (*thisparam == '/') {
644 while (*thisparam == '/') {
645 thisparam++;
646 if (toupperW(*thisparam) == 'D') {
647 opt_d = TRUE;
648 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
649 } else if (toupperW(*thisparam) == 'Y') {
650 opt_y = TRUE;
651 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
652 opt_noty = TRUE;
653 } else if (toupperW(*thisparam) == 'V') {
654 opt_v = TRUE;
655 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
656 } else if (toupperW(*thisparam) == 'N') {
657 opt_n = TRUE;
658 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
659 } else if (toupperW(*thisparam) == 'Z') {
660 opt_z = TRUE;
661 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
662 } else if (toupperW(*thisparam) == 'A') {
663 if (binarymode != 0) {
664 binarymode = 0;
665 WINE_TRACE("Subsequent files will be handled as ASCII\n");
666 if (destination != NULL) {
667 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
668 destination->binarycopy = binarymode;
669 } else if (lastcopyentry != NULL) {
670 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
671 lastcopyentry->binarycopy = binarymode;
674 } else if (toupperW(*thisparam) == 'B') {
675 if (binarymode != 1) {
676 binarymode = 1;
677 WINE_TRACE("Subsequent files will be handled as binary\n");
678 if (destination != NULL) {
679 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
680 destination->binarycopy = binarymode;
681 } else if (lastcopyentry != NULL) {
682 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
683 lastcopyentry->binarycopy = binarymode;
686 } else {
687 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
689 thisparam++;
692 /* This parameter was purely switches, get the next one */
693 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
694 continue;
697 /* We have found something which is not a switch. If could be anything of the form
698 sourcefilename (which could be destination too)
699 + (when filename + filename syntex used)
700 sourcefilename+sourcefilename
701 +sourcefilename
702 +/b[tests show windows then ignores to end of parameter]
705 if (*thisparam=='+') {
706 if (lastcopyentry == NULL) {
707 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
708 errorlevel = 1;
709 goto exitreturn;
710 } else {
711 concatnextfilename = TRUE;
712 anyconcats = TRUE;
715 /* Move to next thing to process */
716 thisparam++;
717 if (*thisparam == 0x00)
718 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
719 continue;
722 /* We have found something to process - build a COPY_FILE block to store it */
723 thiscopy = heap_alloc(sizeof(COPY_FILES));
725 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
726 thiscopy->concatenate = concatnextfilename;
727 thiscopy->binarycopy = binarymode;
728 thiscopy->next = NULL;
730 /* Time to work out the name. Allocate at least enough space (deliberately too much to
731 leave space to append \* to the end) , then copy in character by character. Strip off
732 quotes if we find them. */
733 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
734 thiscopy->name = heap_alloc(len*sizeof(WCHAR));
735 memset(thiscopy->name, 0x00, len);
737 pos1 = thisparam;
738 pos2 = thiscopy->name;
739 inquotes = FALSE;
740 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
741 if (*pos1 == '"') {
742 inquotes = !inquotes;
743 pos1++;
744 } else *pos2++ = *pos1++;
746 *pos2 = 0;
747 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
749 /* This is either the first source, concatenated subsequent source or destination */
750 if (sourcelist == NULL) {
751 WINE_TRACE("Adding as first source part\n");
752 sourcelist = thiscopy;
753 lastcopyentry = thiscopy;
754 } else if (concatnextfilename) {
755 WINE_TRACE("Adding to source file list to be concatenated\n");
756 lastcopyentry->next = thiscopy;
757 lastcopyentry = thiscopy;
758 } else if (destination == NULL) {
759 destination = thiscopy;
760 } else {
761 /* We have processed sources and destinations and still found more to do - invalid */
762 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
763 errorlevel = 1;
764 goto exitreturn;
766 concatnextfilename = FALSE;
768 /* We either need to process the rest of the parameter or move to the next */
769 if (*pos1 == '/' || *pos1 == '+') {
770 thisparam = pos1;
771 continue;
772 } else {
773 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
777 /* Ensure we have at least one source file */
778 if (!sourcelist) {
779 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
780 errorlevel = 1;
781 goto exitreturn;
784 /* Default whether automatic overwriting is on. If we are interactive then
785 we prompt by default, otherwise we overwrite by default
786 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
787 if (opt_noty) prompt = TRUE;
788 else if (opt_y) prompt = FALSE;
789 else {
790 /* By default, we will force the overwrite in batch mode and ask for
791 * confirmation in interactive mode. */
792 prompt = interactive;
793 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
794 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
795 * default behavior. */
796 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
797 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
798 if (!lstrcmpiW (copycmd, parmY))
799 prompt = FALSE;
800 else if (!lstrcmpiW (copycmd, parmNoY))
801 prompt = TRUE;
805 /* Calculate the destination now - if none supplied, it's current dir +
806 filename of first file in list*/
807 if (destination == NULL) {
809 WINE_TRACE("No destination supplied, so need to calculate it\n");
810 strcpyW(destname, dotW);
811 strcatW(destname, slashW);
813 destination = heap_alloc(sizeof(COPY_FILES));
814 if (destination == NULL) goto exitreturn;
815 destination->concatenate = FALSE; /* Not used for destination */
816 destination->binarycopy = binarymode;
817 destination->next = NULL; /* Not used for destination */
818 destination->name = NULL; /* To be filled in */
819 destisdirectory = TRUE;
821 } else {
822 WCHAR *filenamepart;
823 DWORD attributes;
825 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
827 /* Convert to fully qualified path/filename */
828 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
829 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
831 /* If parameter is a directory, ensure it ends in \ */
832 attributes = GetFileAttributesW(destname);
833 if (ends_with_backslash( destname ) ||
834 ((attributes != INVALID_FILE_ATTRIBUTES) &&
835 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
837 destisdirectory = TRUE;
838 if (!ends_with_backslash( destname )) strcatW(destname, slashW);
839 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
843 /* Normally, the destination is the current directory unless we are
844 concatenating, in which case it's current directory plus first filename.
845 Note that if the
846 In addition by default it is a binary copy unless concatenating, when
847 the copy defaults to an ascii copy (stop at EOF). We do not know the
848 first source part yet (until we search) so flag as needing filling in. */
850 if (anyconcats) {
851 /* We have found an a+b type syntax, so destination has to be a filename
852 and we need to default to ascii copying. If we have been supplied a
853 directory as the destination, we need to defer calculating the name */
854 if (destisdirectory) appendfirstsource = TRUE;
855 if (destination->binarycopy == -1) destination->binarycopy = 0;
857 } else if (!destisdirectory) {
858 /* We have been asked to copy to a filename. Default to ascii IF the
859 source contains wildcards (true even if only one match) */
860 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
861 anyconcats = TRUE; /* We really are concatenating to a single file */
862 if (destination->binarycopy == -1) {
863 destination->binarycopy = 0;
865 } else {
866 if (destination->binarycopy == -1) {
867 destination->binarycopy = 1;
872 /* Save away the destination name*/
873 heap_free(destination->name);
874 destination->name = heap_strdupW(destname);
875 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
876 wine_dbgstr_w(destname), appendfirstsource);
878 /* Remember if the destination is a device */
879 if (strncmpW(destination->name, deviceW, strlenW(deviceW)) == 0) {
880 WINE_TRACE("Destination is a device\n");
881 dstisdevice = TRUE;
884 /* Now we need to walk the set of sources, and process each name we come to.
885 If anyconcats is true, we are writing to one file, otherwise we are using
886 the source name each time.
887 If destination exists, prompt for overwrite the first time (if concatenating
888 we ask each time until yes is answered)
889 The first source file we come across must exist (when wildcards expanded)
890 and if concatenating with overwrite prompts, each source file must exist
891 until a yes is answered. */
893 thiscopy = sourcelist;
894 prevcopy = NULL;
896 while (thiscopy != NULL) {
898 WCHAR srcpath[MAX_PATH];
899 const WCHAR *srcname;
900 WCHAR *filenamepart;
901 DWORD attributes;
902 BOOL srcisdevice = FALSE;
904 /* If it was not explicit, we now know whether we are concatenating or not and
905 hence whether to copy as binary or ascii */
906 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
908 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
909 to where the filename portion begins (used for wildcard expansion). */
910 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
911 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
913 /* If parameter is a directory, ensure it ends in \* */
914 attributes = GetFileAttributesW(srcpath);
915 if (ends_with_backslash( srcpath )) {
917 /* We need to know where the filename part starts, so append * and
918 recalculate the full resulting path */
919 strcatW(thiscopy->name, starW);
920 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
921 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
923 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
924 (attributes != INVALID_FILE_ATTRIBUTES) &&
925 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
927 /* We need to know where the filename part starts, so append \* and
928 recalculate the full resulting path */
929 strcatW(thiscopy->name, slashstarW);
930 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
931 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
934 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
935 wine_dbgstr_w(srcpath), anyconcats);
937 /* If the source is a device, just use it, otherwise search */
938 if (strncmpW(srcpath, deviceW, strlenW(deviceW)) == 0) {
939 WINE_TRACE("Source is a device\n");
940 srcisdevice = TRUE;
941 srcname = &srcpath[4]; /* After the \\.\ prefix */
942 } else {
944 /* Loop through all source files */
945 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
946 hff = FindFirstFileW(srcpath, &fd);
947 if (hff != INVALID_HANDLE_VALUE) {
948 srcname = fd.cFileName;
952 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
953 do {
954 WCHAR outname[MAX_PATH];
955 BOOL overwrite;
956 BOOL appendtofirstfile = FALSE;
958 /* Skip . and .., and directories */
959 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
960 WINE_TRACE("Skipping directories\n");
961 } else {
963 /* Build final destination name */
964 strcpyW(outname, destination->name);
965 if (destisdirectory || appendfirstsource) strcatW(outname, srcname);
967 /* Build source name */
968 if (!srcisdevice) strcpyW(filenamepart, srcname);
970 /* Do we just overwrite (we do if we are writing to a device) */
971 overwrite = !prompt;
972 if (dstisdevice || (anyconcats && writtenoneconcat)) {
973 overwrite = TRUE;
976 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
977 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
978 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
979 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
981 if (!writtenoneconcat) {
982 appendtofirstfile = anyconcats && WCMD_IsSameFile(srcpath, outname);
985 /* Prompt before overwriting */
986 if (appendtofirstfile) {
987 overwrite = TRUE;
988 } else if (!overwrite) {
989 DWORD attributes = GetFileAttributesW(outname);
990 if (attributes != INVALID_FILE_ATTRIBUTES) {
991 WCHAR* question;
992 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
993 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
994 LocalFree(question);
996 else overwrite = TRUE;
999 /* If we needed to save away the first filename, do it */
1000 if (appendfirstsource && overwrite) {
1001 heap_free(destination->name);
1002 destination->name = heap_strdupW(outname);
1003 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
1004 appendfirstsource = FALSE;
1005 destisdirectory = FALSE;
1008 /* Do the copy as appropriate */
1009 if (overwrite) {
1010 if (anyconcats && WCMD_IsSameFile(srcpath, outname)) {
1011 /* Silently skip if the destination file is also a source file */
1012 status = TRUE;
1013 } else if (anyconcats && writtenoneconcat) {
1014 if (thiscopy->binarycopy) {
1015 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
1016 } else {
1017 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
1019 } else if (!thiscopy->binarycopy) {
1020 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
1021 } else if (srcisdevice) {
1022 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
1023 } else {
1024 status = CopyFileW(srcpath, outname, FALSE);
1026 if (!status) {
1027 WCMD_print_error ();
1028 errorlevel = 1;
1029 } else {
1030 WINE_TRACE("Copied successfully\n");
1031 if (anyconcats) writtenoneconcat = TRUE;
1033 /* Append EOF if ascii destination and we are not going to add more onto the end
1034 Note: Testing shows windows has an optimization whereas if you have a binary
1035 copy of a file to a single destination (ie concatenation) then it does not add
1036 the EOF, hence the check on the source copy type below. */
1037 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
1038 if (!WCMD_AppendEOF(outname)) {
1039 WCMD_print_error ();
1040 errorlevel = 1;
1046 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1047 if (!srcisdevice) FindClose (hff);
1048 } else {
1049 /* Error if the first file was not found */
1050 if (!anyconcats || !writtenoneconcat) {
1051 WCMD_print_error ();
1052 errorlevel = 1;
1056 /* Step on to the next supplied source */
1057 thiscopy = thiscopy -> next;
1060 /* Append EOF if ascii destination and we were concatenating */
1061 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
1062 if (!WCMD_AppendEOF(destination->name)) {
1063 WCMD_print_error ();
1064 errorlevel = 1;
1068 /* Exit out of the routine, freeing any remaining allocated memory */
1069 exitreturn:
1071 thiscopy = sourcelist;
1072 while (thiscopy != NULL) {
1073 prevcopy = thiscopy;
1074 /* Free up this block*/
1075 thiscopy = thiscopy -> next;
1076 heap_free(prevcopy->name);
1077 heap_free(prevcopy);
1080 /* Free up the destination memory */
1081 if (destination) {
1082 heap_free(destination->name);
1083 heap_free(destination);
1086 return;
1089 /****************************************************************************
1090 * WCMD_create_dir
1092 * Create a directory (and, if needed, any intermediate directories).
1094 * Modifies its argument by replacing slashes temporarily with nulls.
1097 static BOOL create_full_path(WCHAR* path)
1099 WCHAR *p, *start;
1101 /* don't mess with drive letter portion of path, if any */
1102 start = path;
1103 if (path[1] == ':')
1104 start = path+2;
1106 /* Strip trailing slashes. */
1107 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
1108 *p = 0;
1110 /* Step through path, creating intermediate directories as needed. */
1111 /* First component includes drive letter, if any. */
1112 p = start;
1113 for (;;) {
1114 DWORD rv;
1115 /* Skip to end of component */
1116 while (*p == '\\') p++;
1117 while (*p && *p != '\\') p++;
1118 if (!*p) {
1119 /* path is now the original full path */
1120 return CreateDirectoryW(path, NULL);
1122 /* Truncate path, create intermediate directory, and restore path */
1123 *p = 0;
1124 rv = CreateDirectoryW(path, NULL);
1125 *p = '\\';
1126 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1127 return FALSE;
1129 /* notreached */
1130 return FALSE;
1133 void WCMD_create_dir (WCHAR *args) {
1134 int argno = 0;
1135 WCHAR *argN = args;
1137 if (param1[0] == 0x00) {
1138 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1139 return;
1141 /* Loop through all args */
1142 while (TRUE) {
1143 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1144 if (!argN) break;
1145 if (!create_full_path(thisArg)) {
1146 WCMD_print_error ();
1147 errorlevel = 1;
1152 /* Parse the /A options given by the user on the commandline
1153 * into a bitmask of wanted attributes (*wantSet),
1154 * and a bitmask of unwanted attributes (*wantClear).
1156 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1157 static const WCHAR parmA[] = {'/','A','\0'};
1158 WCHAR *p;
1160 /* both are strictly 'out' parameters */
1161 *wantSet=0;
1162 *wantClear=0;
1164 /* For each /A argument */
1165 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1166 /* Skip /A itself */
1167 p += 2;
1169 /* Skip optional : */
1170 if (*p == ':') p++;
1172 /* For each of the attribute specifier chars to this /A option */
1173 for (; *p != 0 && *p != '/'; p++) {
1174 BOOL negate = FALSE;
1175 DWORD mask = 0;
1177 if (*p == '-') {
1178 negate=TRUE;
1179 p++;
1182 /* Convert the attribute specifier to a bit in one of the masks */
1183 switch (*p) {
1184 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1185 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1186 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1187 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1188 default:
1189 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1191 if (negate)
1192 *wantClear |= mask;
1193 else
1194 *wantSet |= mask;
1199 /* If filename part of parameter is * or *.*,
1200 * and neither /Q nor /P options were given,
1201 * prompt the user whether to proceed.
1202 * Returns FALSE if user says no, TRUE otherwise.
1203 * *pPrompted is set to TRUE if the user is prompted.
1204 * (If /P supplied, del will prompt for individual files later.)
1206 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1207 static const WCHAR parmP[] = {'/','P','\0'};
1208 static const WCHAR parmQ[] = {'/','Q','\0'};
1210 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1211 static const WCHAR anyExt[]= {'.','*','\0'};
1212 WCHAR drive[10];
1213 WCHAR dir[MAX_PATH];
1214 WCHAR fname[MAX_PATH];
1215 WCHAR ext[MAX_PATH];
1216 WCHAR fpath[MAX_PATH];
1218 /* Convert path into actual directory spec */
1219 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1220 WCMD_splitpath(fpath, drive, dir, fname, ext);
1222 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1223 if ((strcmpW(fname, starW) == 0) &&
1224 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1226 WCHAR question[MAXSTRING];
1227 static const WCHAR fmt[] = {'%','s',' ','\0'};
1229 /* Caller uses this to suppress "file not found" warning later */
1230 *pPrompted = TRUE;
1232 /* Ask for confirmation */
1233 wsprintfW(question, fmt, fpath);
1234 return WCMD_ask_confirm(question, TRUE, NULL);
1237 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1238 return TRUE;
1241 /* Helper function for WCMD_delete().
1242 * Deletes a single file, directory, or wildcard.
1243 * If /S was given, does it recursively.
1244 * Returns TRUE if a file was deleted.
1246 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1248 static const WCHAR parmP[] = {'/','P','\0'};
1249 static const WCHAR parmS[] = {'/','S','\0'};
1250 static const WCHAR parmF[] = {'/','F','\0'};
1251 DWORD wanted_attrs;
1252 DWORD unwanted_attrs;
1253 BOOL found = FALSE;
1254 WCHAR argCopy[MAX_PATH];
1255 WIN32_FIND_DATAW fd;
1256 HANDLE hff;
1257 WCHAR fpath[MAX_PATH];
1258 WCHAR *p;
1259 BOOL handleParm = TRUE;
1261 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1263 strcpyW(argCopy, thisArg);
1264 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1265 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1267 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1268 /* Skip this arg if user declines to delete *.* */
1269 return FALSE;
1272 /* First, try to delete in the current directory */
1273 hff = FindFirstFileW(argCopy, &fd);
1274 if (hff == INVALID_HANDLE_VALUE) {
1275 handleParm = FALSE;
1276 } else {
1277 found = TRUE;
1280 /* Support del <dirname> by just deleting all files dirname\* */
1281 if (handleParm
1282 && (strchrW(argCopy,'*') == NULL)
1283 && (strchrW(argCopy,'?') == NULL)
1284 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1286 WCHAR modifiedParm[MAX_PATH];
1287 static const WCHAR slashStar[] = {'\\','*','\0'};
1289 strcpyW(modifiedParm, argCopy);
1290 strcatW(modifiedParm, slashStar);
1291 FindClose(hff);
1292 found = TRUE;
1293 WCMD_delete_one(modifiedParm);
1295 } else if (handleParm) {
1297 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1298 strcpyW (fpath, argCopy);
1299 do {
1300 p = strrchrW (fpath, '\\');
1301 if (p != NULL) {
1302 *++p = '\0';
1303 strcatW (fpath, fd.cFileName);
1305 else strcpyW (fpath, fd.cFileName);
1306 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1307 BOOL ok;
1309 /* Handle attribute matching (/A) */
1310 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1311 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1313 /* /P means prompt for each file */
1314 if (ok && strstrW (quals, parmP) != NULL) {
1315 WCHAR* question;
1317 /* Ask for confirmation */
1318 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1319 ok = WCMD_ask_confirm(question, FALSE, NULL);
1320 LocalFree(question);
1323 /* Only proceed if ok to */
1324 if (ok) {
1326 /* If file is read only, and /A:r or /F supplied, delete it */
1327 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1328 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1329 strstrW (quals, parmF) != NULL)) {
1330 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1333 /* Now do the delete */
1334 if (!DeleteFileW(fpath)) WCMD_print_error ();
1338 } while (FindNextFileW(hff, &fd) != 0);
1339 FindClose (hff);
1342 /* Now recurse into all subdirectories handling the parameter in the same way */
1343 if (strstrW (quals, parmS) != NULL) {
1345 WCHAR thisDir[MAX_PATH];
1346 int cPos;
1348 WCHAR drive[10];
1349 WCHAR dir[MAX_PATH];
1350 WCHAR fname[MAX_PATH];
1351 WCHAR ext[MAX_PATH];
1353 /* Convert path into actual directory spec */
1354 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1355 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1357 strcpyW(thisDir, drive);
1358 strcatW(thisDir, dir);
1359 cPos = strlenW(thisDir);
1361 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1363 /* Append '*' to the directory */
1364 thisDir[cPos] = '*';
1365 thisDir[cPos+1] = 0x00;
1367 hff = FindFirstFileW(thisDir, &fd);
1369 /* Remove residual '*' */
1370 thisDir[cPos] = 0x00;
1372 if (hff != INVALID_HANDLE_VALUE) {
1373 DIRECTORY_STACK *allDirs = NULL;
1374 DIRECTORY_STACK *lastEntry = NULL;
1376 do {
1377 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1378 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1379 (strcmpW(fd.cFileName, dotW) != 0)) {
1381 DIRECTORY_STACK *nextDir;
1382 WCHAR subParm[MAX_PATH];
1384 /* Work out search parameter in sub dir */
1385 strcpyW (subParm, thisDir);
1386 strcatW (subParm, fd.cFileName);
1387 strcatW (subParm, slashW);
1388 strcatW (subParm, fname);
1389 strcatW (subParm, ext);
1390 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1392 /* Allocate memory, add to list */
1393 nextDir = heap_alloc(sizeof(DIRECTORY_STACK));
1394 if (allDirs == NULL) allDirs = nextDir;
1395 if (lastEntry != NULL) lastEntry->next = nextDir;
1396 lastEntry = nextDir;
1397 nextDir->next = NULL;
1398 nextDir->dirName = heap_strdupW(subParm);
1400 } while (FindNextFileW(hff, &fd) != 0);
1401 FindClose (hff);
1403 /* Go through each subdir doing the delete */
1404 while (allDirs != NULL) {
1405 DIRECTORY_STACK *tempDir;
1407 tempDir = allDirs->next;
1408 found |= WCMD_delete_one (allDirs->dirName);
1410 heap_free(allDirs->dirName);
1411 heap_free(allDirs);
1412 allDirs = tempDir;
1417 return found;
1420 /****************************************************************************
1421 * WCMD_delete
1423 * Delete a file or wildcarded set.
1425 * Note on /A:
1426 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1427 * - Each set is a pattern, eg /ahr /as-r means
1428 * readonly+hidden OR nonreadonly system files
1429 * - The '-' applies to a single field, ie /a:-hr means read only
1430 * non-hidden files
1433 BOOL WCMD_delete (WCHAR *args) {
1434 int argno;
1435 WCHAR *argN;
1436 BOOL argsProcessed = FALSE;
1437 BOOL foundAny = FALSE;
1439 errorlevel = 0;
1441 for (argno=0; ; argno++) {
1442 BOOL found;
1443 WCHAR *thisArg;
1445 argN = NULL;
1446 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1447 if (!argN)
1448 break; /* no more parameters */
1449 if (argN[0] == '/')
1450 continue; /* skip options */
1452 argsProcessed = TRUE;
1453 found = WCMD_delete_one(thisArg);
1454 if (!found)
1455 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1456 foundAny |= found;
1459 /* Handle no valid args */
1460 if (!argsProcessed)
1461 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1463 return foundAny;
1467 * WCMD_strtrim
1469 * Returns a trimmed version of s with all leading and trailing whitespace removed
1470 * Pre: s non NULL
1473 static WCHAR *WCMD_strtrim(const WCHAR *s)
1475 DWORD len = strlenW(s);
1476 const WCHAR *start = s;
1477 WCHAR* result;
1479 result = heap_alloc((len + 1) * sizeof(WCHAR));
1481 while (isspaceW(*start)) start++;
1482 if (*start) {
1483 const WCHAR *end = s + len - 1;
1484 while (end > start && isspaceW(*end)) end--;
1485 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1486 result[end - start + 1] = '\0';
1487 } else {
1488 result[0] = '\0';
1491 return result;
1494 /****************************************************************************
1495 * WCMD_echo
1497 * Echo input to the screen (or not). We don't try to emulate the bugs
1498 * in DOS (try typing "ECHO ON AGAIN" for an example).
1501 void WCMD_echo (const WCHAR *args)
1503 int count;
1504 const WCHAR *origcommand = args;
1505 WCHAR *trimmed;
1507 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1508 || args[0]==':' || args[0]==';' || args[0]=='/')
1509 args++;
1511 trimmed = WCMD_strtrim(args);
1512 if (!trimmed) return;
1514 count = strlenW(trimmed);
1515 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1516 && origcommand[0]!=';' && origcommand[0]!='/') {
1517 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1518 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1519 heap_free(trimmed);
1520 return;
1523 if (lstrcmpiW(trimmed, onW) == 0)
1524 echo_mode = TRUE;
1525 else if (lstrcmpiW(trimmed, offW) == 0)
1526 echo_mode = FALSE;
1527 else {
1528 WCMD_output_asis (args);
1529 WCMD_output_asis (newlineW);
1531 heap_free(trimmed);
1534 /*****************************************************************************
1535 * WCMD_part_execute
1537 * Execute a command, and any && or bracketed follow on to the command. The
1538 * first command to be executed may not be at the front of the
1539 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1541 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1542 BOOL isIF, BOOL executecmds)
1544 CMD_LIST *curPosition = *cmdList;
1545 int myDepth = (*cmdList)->bracketDepth;
1547 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d)\n", cmdList, wine_dbgstr_w(firstcmd),
1548 executecmds);
1550 /* Skip leading whitespace between condition and the command */
1551 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1553 /* Process the first command, if there is one */
1554 if (executecmds && firstcmd && *firstcmd) {
1555 WCHAR *command = heap_strdupW(firstcmd);
1556 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1557 heap_free(command);
1561 /* If it didn't move the position, step to next command */
1562 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1564 /* Process any other parts of the command */
1565 if (*cmdList) {
1566 BOOL processThese = executecmds;
1568 while (*cmdList) {
1569 static const WCHAR ifElse[] = {'e','l','s','e'};
1571 /* execute all appropriate commands */
1572 curPosition = *cmdList;
1574 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d) processThese(%d)\n",
1575 *cmdList,
1576 (*cmdList)->prevDelim,
1577 (*cmdList)->bracketDepth,
1578 myDepth,
1579 processThese);
1581 /* Execute any statements appended to the line */
1582 /* FIXME: Only if previous call worked for && or failed for || */
1583 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1584 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1585 if (processThese && (*cmdList)->command) {
1586 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1587 cmdList, FALSE);
1589 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1591 /* Execute any appended to the statement with (...) */
1592 } else if ((*cmdList)->bracketDepth > myDepth) {
1593 if (processThese) {
1594 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1595 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1597 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1599 /* End of the command - does 'ELSE ' follow as the next command? */
1600 } else {
1601 if (isIF
1602 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1603 (*cmdList)->command)) {
1605 /* Swap between if and else processing */
1606 processThese = !executecmds;
1608 /* Process the ELSE part */
1609 if (processThese) {
1610 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1611 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1613 /* Skip leading whitespace between condition and the command */
1614 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1615 if (*cmd) {
1616 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1618 } else {
1619 /* Loop skipping all commands until we get back to the current
1620 depth, including skipping commands and their subsequent
1621 pipes (eg cmd | prog) */
1622 do {
1623 *cmdList = (*cmdList)->nextcommand;
1624 } while (*cmdList &&
1625 ((*cmdList)->bracketDepth > myDepth ||
1626 (*cmdList)->prevDelim));
1628 /* After the else is complete, we need to now process subsequent commands */
1629 processThese = TRUE;
1631 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1632 } else if (!processThese) {
1633 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1634 WINE_TRACE("Ignore the next command as well (next = %p)\n", *cmdList);
1635 } else {
1636 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1637 break;
1642 return;
1645 /*****************************************************************************
1646 * WCMD_parse_forf_options
1648 * Parses the for /f 'options', extracting the values and validating the
1649 * keywords. Note all keywords are optional.
1650 * Parameters:
1651 * options [I] The unparsed parameter string
1652 * eol [O] Set to the comment character (eol=x)
1653 * skip [O] Set to the number of lines to skip (skip=xx)
1654 * delims [O] Set to the token delimiters (delims=)
1655 * tokens [O] Set to the requested tokens, as provided (tokens=)
1656 * usebackq [O] Set to TRUE if usebackq found
1658 * Returns TRUE on success, FALSE on syntax error
1661 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1662 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1665 WCHAR *pos = options;
1666 int len = strlenW(pos);
1667 static const WCHAR eolW[] = {'e','o','l','='};
1668 static const WCHAR skipW[] = {'s','k','i','p','='};
1669 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1670 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1671 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1672 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1673 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1675 /* Initialize to defaults */
1676 strcpyW(delims, forf_defaultdelims);
1677 strcpyW(tokens, forf_defaulttokens);
1678 *eol = 0;
1679 *skip = 0;
1680 *usebackq = FALSE;
1682 /* Strip (optional) leading and trailing quotes */
1683 if ((*pos == '"') && (pos[len-1] == '"')) {
1684 pos[len-1] = 0;
1685 pos++;
1688 /* Process each keyword */
1689 while (pos && *pos) {
1690 if (*pos == ' ' || *pos == '\t') {
1691 pos++;
1693 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1694 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1695 pos, sizeof(eolW)/sizeof(WCHAR),
1696 eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1697 *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1698 pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1699 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1701 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1702 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1703 pos, sizeof(skipW)/sizeof(WCHAR),
1704 skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1705 WCHAR *nextchar = NULL;
1706 pos = pos + sizeof(skipW)/sizeof(WCHAR);
1707 *skip = strtoulW(pos, &nextchar, 0);
1708 WINE_TRACE("Found skip as %d lines\n", *skip);
1709 pos = nextchar;
1711 /* Save if usebackq semantics are in effect */
1712 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1713 pos, sizeof(usebackqW)/sizeof(WCHAR),
1714 usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1715 *usebackq = TRUE;
1716 pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1717 WINE_TRACE("Found usebackq\n");
1719 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1720 if you finish the optionsroot string with delims= otherwise the space is
1721 just a token delimiter! */
1722 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1723 pos, sizeof(delimsW)/sizeof(WCHAR),
1724 delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1725 int i=0;
1727 pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1728 while (*pos && *pos != ' ') {
1729 delims[i++] = *pos;
1730 pos++;
1732 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1733 delims[i++] = 0; /* Null terminate the delims */
1734 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1736 /* Save the tokens being requested */
1737 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1738 pos, sizeof(tokensW)/sizeof(WCHAR),
1739 tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1740 int i=0;
1742 pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1743 while (*pos && *pos != ' ') {
1744 tokens[i++] = *pos;
1745 pos++;
1747 tokens[i++] = 0; /* Null terminate the tokens */
1748 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1750 } else {
1751 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1752 return FALSE;
1755 return TRUE;
1758 /*****************************************************************************
1759 * WCMD_add_dirstowalk
1761 * When recursing through directories (for /r), we need to add to the list of
1762 * directories still to walk, any subdirectories of the one we are processing.
1764 * Parameters
1765 * options [I] The remaining list of directories still to process
1767 * Note this routine inserts the subdirectories found between the entry being
1768 * processed, and any other directory still to be processed, mimicking what
1769 * Windows does
1771 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1772 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1773 WCHAR fullitem[MAX_PATH];
1774 WIN32_FIND_DATAW fd;
1775 HANDLE hff;
1777 /* Build a generic search and add all directories on the list of directories
1778 still to walk */
1779 strcpyW(fullitem, dirsToWalk->dirName);
1780 strcatW(fullitem, slashstarW);
1781 hff = FindFirstFileW(fullitem, &fd);
1782 if (hff != INVALID_HANDLE_VALUE) {
1783 do {
1784 WINE_TRACE("Looking for subdirectories\n");
1785 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1786 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1787 (strcmpW(fd.cFileName, dotW) != 0))
1789 /* Allocate memory, add to list */
1790 DIRECTORY_STACK *toWalk = heap_alloc(sizeof(DIRECTORY_STACK));
1791 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1792 toWalk->next = remainingDirs->next;
1793 remainingDirs->next = toWalk;
1794 remainingDirs = toWalk;
1795 toWalk->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1796 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1797 strcatW(toWalk->dirName, slashW);
1798 strcatW(toWalk->dirName, fd.cFileName);
1799 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1800 toWalk, toWalk->next);
1802 } while (FindNextFileW(hff, &fd) != 0);
1803 WINE_TRACE("Finished adding all subdirectories\n");
1804 FindClose (hff);
1808 /**************************************************************************
1809 * WCMD_for_nexttoken
1811 * Parse the token= line, identifying the next highest number not processed
1812 * so far. Count how many tokens are referred (including duplicates) and
1813 * optionally return that, plus optionally indicate if the tokens= line
1814 * ends in a star.
1816 * Parameters:
1817 * lasttoken [I] - Identifies the token index of the last one
1818 * returned so far (-1 used for first loop)
1819 * tokenstr [I] - The specified tokens= line
1820 * firstCmd [O] - Optionally indicate how many tokens are listed
1821 * doAll [O] - Optionally indicate if line ends with *
1822 * duplicates [O] - Optionally indicate if there is any evidence of
1823 * overlaying tokens in the string
1824 * Note the caller should keep a running track of duplicates as the tokens
1825 * are recursively passed. If any have duplicates, then the * token should
1826 * not be honoured.
1828 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1829 int *totalfound, BOOL *doall,
1830 BOOL *duplicates)
1832 WCHAR *pos = tokenstr;
1833 int nexttoken = -1;
1835 if (totalfound) *totalfound = 0;
1836 if (doall) *doall = FALSE;
1837 if (duplicates) *duplicates = FALSE;
1839 WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken,
1840 wine_dbgstr_w(tokenstr), nexttoken);
1842 /* Loop through the token string, parsing it. Valid syntax is:
1843 token=m or x-y with comma delimiter and optionally * to finish*/
1844 while (*pos) {
1845 int nextnumber1, nextnumber2 = -1;
1846 WCHAR *nextchar;
1848 /* It is valid syntax tokens=* which just means get whole line */
1849 if (*pos == '*') {
1850 if (doall) *doall = TRUE;
1851 if (totalfound) (*totalfound)++;
1852 nexttoken = 0;
1853 break;
1856 /* Get the next number */
1857 nextnumber1 = strtoulW(pos, &nextchar, 10);
1859 /* If it is followed by a minus, it's a range, so get the next one as well */
1860 if (*nextchar == '-') {
1861 nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1863 /* We want to return the lowest number that is higher than lasttoken
1864 but only if range is positive */
1865 if (nextnumber2 >= nextnumber1 &&
1866 lasttoken < nextnumber2) {
1868 int nextvalue;
1869 if (nexttoken == -1) {
1870 nextvalue = max(nextnumber1, (lasttoken+1));
1871 } else {
1872 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1875 /* Flag if duplicates identified */
1876 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1878 nexttoken = nextvalue;
1881 /* Update the running total for the whole range */
1882 if (nextnumber2 >= nextnumber1 && totalfound) {
1883 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1886 } else {
1887 if (totalfound) (*totalfound)++;
1889 /* See if the number found is one we have already seen */
1890 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1892 /* We want to return the lowest number that is higher than lasttoken */
1893 if (lasttoken < nextnumber1 &&
1894 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1895 nexttoken = nextnumber1;
1900 /* Remember if it is followed by a star, and if it is indicate a need to
1901 show all tokens, unless a duplicate has been found */
1902 if (*nextchar == '*') {
1903 if (doall) *doall = TRUE;
1904 if (totalfound) (*totalfound)++;
1907 /* Step on to the next character */
1908 pos = nextchar;
1909 if (*pos) pos++;
1912 /* Return result */
1913 if (nexttoken == -1) nexttoken = lasttoken;
1914 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1915 if (totalfound) WINE_TRACE("Found total tokens in total %d\n", *totalfound);
1916 if (doall && *doall) WINE_TRACE("Request for all tokens found\n");
1917 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1918 return nexttoken;
1921 /**************************************************************************
1922 * WCMD_parse_line
1924 * When parsing file or string contents (for /f), once the string to parse
1925 * has been identified, handle the various options and call the do part
1926 * if appropriate.
1928 * Parameters:
1929 * cmdStart [I] - Identifies the list of commands making up the
1930 * for loop body (especially if brackets in use)
1931 * firstCmd [I] - The textual start of the command after the DO
1932 * which is within the first item of cmdStart
1933 * cmdEnd [O] - Identifies where to continue after the DO
1934 * variable [I] - The variable identified on the for line
1935 * buffer [I] - The string to parse
1936 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1937 * forf_skip [I/O] - How many lines to skip first
1938 * forf_eol [I] - The 'end of line' (comment) character
1939 * forf_delims [I] - The delimiters to use when breaking the string apart
1940 * forf_tokens [I] - The tokens to use when breaking the string apart
1942 static void WCMD_parse_line(CMD_LIST *cmdStart,
1943 const WCHAR *firstCmd,
1944 CMD_LIST **cmdEnd,
1945 const WCHAR variable,
1946 WCHAR *buffer,
1947 BOOL *doExecuted,
1948 int *forf_skip,
1949 WCHAR forf_eol,
1950 WCHAR *forf_delims,
1951 WCHAR *forf_tokens) {
1953 WCHAR *parm;
1954 FOR_CONTEXT oldcontext;
1955 int varidx, varoffset;
1956 int nexttoken, lasttoken = -1;
1957 BOOL starfound = FALSE;
1958 BOOL thisduplicate = FALSE;
1959 BOOL anyduplicates = FALSE;
1960 int totalfound;
1962 /* Skip lines if requested */
1963 if (*forf_skip) {
1964 (*forf_skip)--;
1965 return;
1968 /* Save away any existing for variable context (e.g. nested for loops) */
1969 oldcontext = forloopcontext;
1971 /* Extract the parameters based on the tokens= value (There will always
1972 be some value, as if it is not supplied, it defaults to tokens=1).
1973 Rough logic:
1974 Count how many tokens are named in the line, identify the lowest
1975 Empty (set to null terminated string) that number of named variables
1976 While lasttoken != nextlowest
1977 %letter = parameter number 'nextlowest'
1978 letter++ (if >26 or >52 abort)
1979 Go through token= string finding next lowest number
1980 If token ends in * set %letter = raw position of token(nextnumber+1)
1982 lasttoken = -1;
1983 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1984 &starfound, &thisduplicate);
1985 varidx = FOR_VAR_IDX(variable);
1987 /* Empty out variables */
1988 for (varoffset=0;
1989 varidx >= 0 && varoffset<totalfound && (((varidx%26) + varoffset) < 26);
1990 varoffset++) {
1991 forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
1994 /* Loop extracting the tokens
1995 Note: nexttoken of 0 means there were no tokens requested, to handle
1996 the special case of tokens=* */
1997 varoffset = 0;
1998 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1999 while (varidx >= 0 && (nexttoken > 0 && (nexttoken > lasttoken))) {
2000 anyduplicates |= thisduplicate;
2002 /* Extract the token number requested and set into the next variable context */
2003 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims);
2004 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
2005 varidx + varoffset, wine_dbgstr_w(parm));
2006 if (varidx >=0) {
2007 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2008 varoffset++;
2009 if (((varidx%26)+varoffset) >= 26) break;
2012 /* Find the next token */
2013 lasttoken = nexttoken;
2014 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
2015 &starfound, &thisduplicate);
2018 /* If all the rest of the tokens were requested, and there is still space in
2019 the variable range, write them now */
2020 if (!anyduplicates && starfound && varidx >= 0 && (((varidx%26) + varoffset) < 26)) {
2021 nexttoken++;
2022 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
2023 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2024 varidx + varoffset, wine_dbgstr_w(parm));
2025 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2028 /* Execute the body of the foor loop with these values */
2029 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
2030 CMD_LIST *thisCmdStart = cmdStart;
2031 *doExecuted = TRUE;
2032 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
2033 *cmdEnd = thisCmdStart;
2036 /* Free the duplicated strings, and restore the context */
2037 if (varidx >=0) {
2038 int i;
2039 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
2040 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
2041 (forloopcontext.variable[i] != nullW)) {
2042 heap_free(forloopcontext.variable[i]);
2047 /* Restore the original for variable contextx */
2048 forloopcontext = oldcontext;
2051 /**************************************************************************
2052 * WCMD_forf_getinputhandle
2054 * Return a file handle which can be used for reading the input lines,
2055 * either to a specific file (which may be quote delimited as we have to
2056 * read the parameters in raw mode) or to a command which we need to
2057 * execute. The command being executed runs in its own shell and stores
2058 * its data in a temporary file.
2060 * Parameters:
2061 * usebackq [I] - Indicates whether usebackq is in effect or not
2062 * itemStr [I] - The item to be handled, either a filename or
2063 * whole command string to execute
2064 * iscmd [I] - Identifies whether this is a command or not
2066 * Returns a file handle which can be used to read the input lines from.
2068 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2069 WCHAR temp_str[MAX_PATH];
2070 WCHAR temp_file[MAX_PATH];
2071 WCHAR temp_cmd[MAXSTRING];
2072 HANDLE hinput = INVALID_HANDLE_VALUE;
2073 static const WCHAR redirOutW[] = {'>','%','s','\0'};
2074 static const WCHAR cmdW[] = {'C','M','D','\0'};
2075 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
2076 '/','C',' ','"','%','s','"','\0'};
2078 /* Remove leading and trailing character */
2079 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2080 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2081 (!iscmd && (itemstr[0] == '"' && usebackq)))
2083 itemstr[strlenW(itemstr)-1] = 0x00;
2084 itemstr++;
2087 if (iscmd) {
2088 /* Get temp filename */
2089 GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
2090 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
2092 /* Redirect output to the temporary file */
2093 wsprintfW(temp_str, redirOutW, temp_file);
2094 wsprintfW(temp_cmd, cmdslashcW, itemstr);
2095 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2096 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2097 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2099 /* Open the file, read line by line and process */
2100 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2101 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2103 } else {
2104 /* Open the file, read line by line and process */
2105 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2106 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2107 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2109 return hinput;
2112 /**************************************************************************
2113 * WCMD_for
2115 * Batch file loop processing.
2117 * On entry: cmdList contains the syntax up to the set
2118 * next cmdList and all in that bracket contain the set data
2119 * next cmdlist contains the DO cmd
2120 * following that is either brackets or && entries (as per if)
2124 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2126 WIN32_FIND_DATAW fd;
2127 HANDLE hff;
2128 int i;
2129 static const WCHAR inW[] = {'i','n'};
2130 static const WCHAR doW[] = {'d','o'};
2131 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2132 WCHAR variable[4];
2133 int varidx = -1;
2134 WCHAR *oldvariablevalue;
2135 WCHAR *firstCmd;
2136 int thisDepth;
2137 WCHAR optionsRoot[MAX_PATH];
2138 DIRECTORY_STACK *dirsToWalk = NULL;
2139 BOOL expandDirs = FALSE;
2140 BOOL useNumbers = FALSE;
2141 BOOL doFileset = FALSE;
2142 BOOL doRecurse = FALSE;
2143 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2144 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2145 int itemNum;
2146 CMD_LIST *thisCmdStart;
2147 int parameterNo = 0;
2148 WCHAR forf_eol = 0;
2149 int forf_skip = 0;
2150 WCHAR forf_delims[256];
2151 WCHAR forf_tokens[MAXSTRING];
2152 BOOL forf_usebackq = FALSE;
2154 /* Handle optional qualifiers (multiple are allowed) */
2155 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2157 optionsRoot[0] = 0;
2158 while (thisArg && *thisArg == '/') {
2159 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2160 thisArg++;
2161 switch (toupperW(*thisArg)) {
2162 case 'D': expandDirs = TRUE; break;
2163 case 'L': useNumbers = TRUE; break;
2165 /* Recursive is special case - /R can have an optional path following it */
2166 /* filenamesets are another special case - /F can have an optional options following it */
2167 case 'R':
2168 case 'F':
2170 /* When recursing directories, use current directory as the starting point unless
2171 subsequently overridden */
2172 doRecurse = (toupperW(*thisArg) == 'R');
2173 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
2175 doFileset = (toupperW(*thisArg) == 'F');
2177 /* Retrieve next parameter to see if is root/options (raw form required
2178 with for /f, or unquoted in for /r) */
2179 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2181 /* Next parm is either qualifier, path/options or variable -
2182 only care about it if it is the path/options */
2183 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2184 parameterNo++;
2185 strcpyW(optionsRoot, thisArg);
2187 break;
2189 default:
2190 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2193 /* Step to next token */
2194 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2197 /* Ensure line continues with variable */
2198 if (*thisArg != '%') {
2199 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2200 return;
2203 /* With for /f parse the options if provided */
2204 if (doFileset) {
2205 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2206 forf_delims, forf_tokens, &forf_usebackq))
2208 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2209 return;
2212 /* Set up the list of directories to recurse if we are going to */
2213 } else if (doRecurse) {
2214 /* Allocate memory, add to list */
2215 dirsToWalk = heap_alloc(sizeof(DIRECTORY_STACK));
2216 dirsToWalk->next = NULL;
2217 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2218 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2221 /* Variable should follow */
2222 strcpyW(variable, thisArg);
2223 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2224 varidx = FOR_VAR_IDX(variable[1]);
2226 /* Ensure line continues with IN */
2227 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2228 if (!thisArg
2229 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2230 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
2231 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
2232 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2233 return;
2236 /* Save away where the set of data starts and the variable */
2237 thisDepth = (*cmdList)->bracketDepth;
2238 *cmdList = (*cmdList)->nextcommand;
2239 setStart = (*cmdList);
2241 /* Skip until the close bracket */
2242 WINE_TRACE("Searching %p as the set\n", *cmdList);
2243 while (*cmdList &&
2244 (*cmdList)->command != NULL &&
2245 (*cmdList)->bracketDepth > thisDepth) {
2246 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2247 *cmdList = (*cmdList)->nextcommand;
2250 /* Skip the close bracket, if there is one */
2251 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2253 /* Syntax error if missing close bracket, or nothing following it
2254 and once we have the complete set, we expect a DO */
2255 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2256 if ((*cmdList == NULL)
2257 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
2259 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2260 return;
2263 cmdEnd = *cmdList;
2265 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2266 mode, or once for the rest of the time. */
2267 do {
2269 /* Save away the starting position for the commands (and offset for the
2270 first one) */
2271 cmdStart = *cmdList;
2272 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2273 itemNum = 0;
2275 /* If we are recursing directories (ie /R), add all sub directories now, then
2276 prefix the root when searching for the item */
2277 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2279 thisSet = setStart;
2280 /* Loop through all set entries */
2281 while (thisSet &&
2282 thisSet->command != NULL &&
2283 thisSet->bracketDepth >= thisDepth) {
2285 /* Loop through all entries on the same line */
2286 WCHAR *staticitem;
2287 WCHAR *itemStart;
2288 WCHAR buffer[MAXSTRING];
2290 WINE_TRACE("Processing for set %p\n", thisSet);
2291 i = 0;
2292 while (*(staticitem = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2295 * If the parameter within the set has a wildcard then search for matching files
2296 * otherwise do a literal substitution.
2298 static const WCHAR wildcards[] = {'*','?','\0'};
2300 /* Take a copy of the item returned from WCMD_parameter as it is held in a
2301 static buffer which can be overwritten during parsing of the for body */
2302 WCHAR item[MAXSTRING];
2303 strcpyW(item, staticitem);
2305 thisCmdStart = cmdStart;
2307 itemNum++;
2308 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2310 if (!useNumbers && !doFileset) {
2311 WCHAR fullitem[MAX_PATH];
2312 int prefixlen = 0;
2314 /* Now build the item to use / search for in the specified directory,
2315 as it is fully qualified in the /R case */
2316 if (dirsToWalk) {
2317 strcpyW(fullitem, dirsToWalk->dirName);
2318 strcatW(fullitem, slashW);
2319 strcatW(fullitem, item);
2320 } else {
2321 WCHAR *prefix = strrchrW(item, '\\');
2322 if (prefix) prefixlen = (prefix - item) + 1;
2323 strcpyW(fullitem, item);
2326 if (strpbrkW (fullitem, wildcards)) {
2327 hff = FindFirstFileW(fullitem, &fd);
2328 if (hff != INVALID_HANDLE_VALUE) {
2329 do {
2330 BOOL isDirectory = FALSE;
2332 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2334 /* Handle as files or dirs appropriately, but ignore . and .. */
2335 if (isDirectory == expandDirs &&
2336 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2337 (strcmpW(fd.cFileName, dotW) != 0))
2339 thisCmdStart = cmdStart;
2340 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2342 if (doRecurse) {
2343 strcpyW(fullitem, dirsToWalk->dirName);
2344 strcatW(fullitem, slashW);
2345 strcatW(fullitem, fd.cFileName);
2346 } else {
2347 if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
2348 fullitem[prefixlen] = 0x00;
2349 strcatW(fullitem, fd.cFileName);
2351 doExecuted = TRUE;
2353 /* Save away any existing for variable context (e.g. nested for loops)
2354 and restore it after executing the body of this for loop */
2355 if (varidx >= 0) {
2356 oldvariablevalue = forloopcontext.variable[varidx];
2357 forloopcontext.variable[varidx] = fullitem;
2359 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2360 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2362 cmdEnd = thisCmdStart;
2364 } while (FindNextFileW(hff, &fd) != 0);
2365 FindClose (hff);
2367 } else {
2368 doExecuted = TRUE;
2370 /* Save away any existing for variable context (e.g. nested for loops)
2371 and restore it after executing the body of this for loop */
2372 if (varidx >= 0) {
2373 oldvariablevalue = forloopcontext.variable[varidx];
2374 forloopcontext.variable[varidx] = fullitem;
2376 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2377 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2379 cmdEnd = thisCmdStart;
2382 } else if (useNumbers) {
2383 /* Convert the first 3 numbers to signed longs and save */
2384 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2385 /* else ignore them! */
2387 /* Filesets - either a list of files, or a command to run and parse the output */
2388 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2389 (forf_usebackq && *itemStart != '\''))) {
2391 HANDLE input;
2392 WCHAR *itemparm;
2394 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2395 wine_dbgstr_w(item));
2397 /* If backquote or single quote, we need to launch that command
2398 and parse the results - use a temporary file */
2399 if ((forf_usebackq && *itemStart == '`') ||
2400 (!forf_usebackq && *itemStart == '\'')) {
2402 /* Use itemstart because the command is the whole set, not just the first token */
2403 itemparm = itemStart;
2404 } else {
2406 /* Use item because the file to process is just the first item in the set */
2407 itemparm = item;
2409 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2411 /* Process the input file */
2412 if (input == INVALID_HANDLE_VALUE) {
2413 WCMD_print_error ();
2414 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2415 errorlevel = 1;
2416 return; /* FOR loop aborts at first failure here */
2418 } else {
2420 /* Read line by line until end of file */
2421 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2422 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2423 &forf_skip, forf_eol, forf_delims, forf_tokens);
2424 buffer[0] = 0;
2426 CloseHandle (input);
2429 /* When we have processed the item as a whole command, abort future set processing */
2430 if (itemparm==itemStart) {
2431 thisSet = NULL;
2432 break;
2435 /* Filesets - A string literal */
2436 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2437 (forf_usebackq && *itemStart == '\''))) {
2439 /* Remove leading and trailing character, ready to parse with delims= delimiters
2440 Note that the last quote is removed from the set and the string terminates
2441 there to mimic windows */
2442 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2443 if (strend) {
2444 *strend = 0x00;
2445 itemStart++;
2448 /* Copy the item away from the global buffer used by WCMD_parameter */
2449 strcpyW(buffer, itemStart);
2450 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2451 &forf_skip, forf_eol, forf_delims, forf_tokens);
2453 /* Only one string can be supplied in the whole set, abort future set processing */
2454 thisSet = NULL;
2455 break;
2458 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2459 i++;
2462 /* Move onto the next set line */
2463 if (thisSet) thisSet = thisSet->nextcommand;
2466 /* If /L is provided, now run the for loop */
2467 if (useNumbers) {
2468 WCHAR thisNum[20];
2469 static const WCHAR fmt[] = {'%','d','\0'};
2471 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2472 numbers[0], numbers[2], numbers[1]);
2473 for (i=numbers[0];
2474 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2475 i=i + numbers[1]) {
2477 sprintfW(thisNum, fmt, i);
2478 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2480 thisCmdStart = cmdStart;
2481 doExecuted = TRUE;
2483 /* Save away any existing for variable context (e.g. nested for loops)
2484 and restore it after executing the body of this for loop */
2485 if (varidx >= 0) {
2486 oldvariablevalue = forloopcontext.variable[varidx];
2487 forloopcontext.variable[varidx] = thisNum;
2489 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2490 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2492 cmdEnd = thisCmdStart;
2495 /* If we are walking directories, move on to any which remain */
2496 if (dirsToWalk != NULL) {
2497 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2498 heap_free(dirsToWalk->dirName);
2499 heap_free(dirsToWalk);
2500 dirsToWalk = nextDir;
2501 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2502 wine_dbgstr_w(dirsToWalk->dirName));
2503 else WINE_TRACE("Finished all directories.\n");
2506 } while (dirsToWalk != NULL);
2508 /* Now skip over the do part if we did not perform the for loop so far.
2509 We store in cmdEnd the next command after the do block, but we only
2510 know this if something was run. If it has not been, we need to calculate
2511 it. */
2512 if (!doExecuted) {
2513 thisCmdStart = cmdStart;
2514 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2515 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2516 cmdEnd = thisCmdStart;
2519 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2520 all processing, OR it should be pointing to the end of && processing OR
2521 it should be pointing at the NULL end of bracket for the DO. The return
2522 value needs to be the NEXT command to execute, which it either is, or
2523 we need to step over the closing bracket */
2524 *cmdList = cmdEnd;
2525 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2528 /**************************************************************************
2529 * WCMD_give_help
2531 * Simple on-line help. Help text is stored in the resource file.
2534 void WCMD_give_help (const WCHAR *args)
2536 size_t i;
2538 args = WCMD_skip_leading_spaces((WCHAR*) args);
2539 if (strlenW(args) == 0) {
2540 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2542 else {
2543 /* Display help message for builtin commands */
2544 for (i=0; i<=WCMD_EXIT; i++) {
2545 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2546 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2547 WCMD_output_asis (WCMD_LoadMessage(i));
2548 return;
2551 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2552 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2553 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2554 args, -1, externals[i], -1) == CSTR_EQUAL) {
2555 WCHAR cmd[128];
2556 static const WCHAR helpW[] = {' ', '/','?','\0'};
2557 strcpyW(cmd, args);
2558 strcatW(cmd, helpW);
2559 WCMD_run_program(cmd, FALSE);
2560 return;
2563 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2565 return;
2568 /****************************************************************************
2569 * WCMD_go_to
2571 * Batch file jump instruction. Not the most efficient algorithm ;-)
2572 * Prints error message if the specified label cannot be found - the file pointer is
2573 * then at EOF, effectively stopping the batch file.
2574 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2577 void WCMD_goto (CMD_LIST **cmdList) {
2579 WCHAR string[MAX_PATH];
2580 WCHAR *labelend = NULL;
2581 const WCHAR labelEndsW[] = {'>','<','|','&',' ',':','\t','\0'};
2583 /* Do not process any more parts of a processed multipart or multilines command */
2584 if (cmdList) *cmdList = NULL;
2586 if (context != NULL) {
2587 WCHAR *paramStart = param1, *str;
2588 static const WCHAR eofW[] = {':','e','o','f','\0'};
2590 if (param1[0] == 0x00) {
2591 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2592 return;
2595 /* Handle special :EOF label */
2596 if (lstrcmpiW (eofW, param1) == 0) {
2597 context -> skip_rest = TRUE;
2598 return;
2601 /* Support goto :label as well as goto label plus remove trailing chars */
2602 if (*paramStart == ':') paramStart++;
2603 labelend = strpbrkW(paramStart, labelEndsW);
2604 if (labelend) *labelend = 0x00;
2605 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
2607 /* Loop through potentially twice - once from current file position
2608 through to the end, and second time from start to current file
2609 position */
2610 if (*paramStart) {
2611 int loop;
2612 LARGE_INTEGER startli;
2613 for (loop=0; loop<2; loop++) {
2614 if (loop==0) {
2615 /* On first loop, save the file size */
2616 startli.QuadPart = 0;
2617 startli.u.LowPart = SetFilePointer(context -> h, startli.u.LowPart,
2618 &startli.u.HighPart, FILE_CURRENT);
2619 } else {
2620 /* On second loop, start at the beginning of the file */
2621 WINE_TRACE("Label not found, trying from beginning of file\n");
2622 if (loop==1) SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2625 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2626 str = string;
2628 /* Ignore leading whitespace or no-echo character */
2629 while (*str=='@' || isspaceW (*str)) str++;
2631 /* If the first real character is a : then this is a label */
2632 if (*str == ':') {
2633 str++;
2635 /* Skip spaces between : and label */
2636 while (isspaceW (*str)) str++;
2637 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
2639 /* Label ends at whitespace or redirection characters */
2640 labelend = strpbrkW(str, labelEndsW);
2641 if (labelend) *labelend = 0x00;
2642 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
2644 if (lstrcmpiW (str, paramStart) == 0) return;
2647 /* See if we have gone beyond the end point if second time through */
2648 if (loop==1) {
2649 LARGE_INTEGER curli;
2650 curli.QuadPart = 0;
2651 curli.u.LowPart = SetFilePointer(context -> h, curli.u.LowPart,
2652 &curli.u.HighPart, FILE_CURRENT);
2653 if (curli.QuadPart > startli.QuadPart) {
2654 WINE_TRACE("Reached wrap point, label not found\n");
2655 break;
2662 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2663 context -> skip_rest = TRUE;
2665 return;
2668 /*****************************************************************************
2669 * WCMD_pushd
2671 * Push a directory onto the stack
2674 void WCMD_pushd (const WCHAR *args)
2676 struct env_stack *curdir;
2677 WCHAR *thisdir;
2678 static const WCHAR parmD[] = {'/','D','\0'};
2680 if (strchrW(args, '/') != NULL) {
2681 SetLastError(ERROR_INVALID_PARAMETER);
2682 WCMD_print_error();
2683 return;
2686 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2687 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2688 if( !curdir || !thisdir ) {
2689 LocalFree(curdir);
2690 LocalFree(thisdir);
2691 WINE_ERR ("out of memory\n");
2692 return;
2695 /* Change directory using CD code with /D parameter */
2696 strcpyW(quals, parmD);
2697 GetCurrentDirectoryW (1024, thisdir);
2698 errorlevel = 0;
2699 WCMD_setshow_default(args);
2700 if (errorlevel) {
2701 LocalFree(curdir);
2702 LocalFree(thisdir);
2703 return;
2704 } else {
2705 curdir -> next = pushd_directories;
2706 curdir -> strings = thisdir;
2707 if (pushd_directories == NULL) {
2708 curdir -> u.stackdepth = 1;
2709 } else {
2710 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2712 pushd_directories = curdir;
2717 /*****************************************************************************
2718 * WCMD_popd
2720 * Pop a directory from the stack
2723 void WCMD_popd (void) {
2724 struct env_stack *temp = pushd_directories;
2726 if (!pushd_directories)
2727 return;
2729 /* pop the old environment from the stack, and make it the current dir */
2730 pushd_directories = temp->next;
2731 SetCurrentDirectoryW(temp->strings);
2732 LocalFree (temp->strings);
2733 LocalFree (temp);
2736 /*******************************************************************
2737 * evaluate_if_comparison
2739 * Evaluates an "if" comparison operation
2741 * PARAMS
2742 * leftOperand [I] left operand, non NULL
2743 * operator [I] "if" binary comparison operator, non NULL
2744 * rightOperand [I] right operand, non NULL
2745 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2747 * RETURNS
2748 * Success: 1 if operator applied to the operands evaluates to TRUE
2749 * 0 if operator applied to the operands evaluates to FALSE
2750 * Failure: -1 if operator is not recognized
2752 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2753 const WCHAR *rightOperand, int caseInsensitive)
2755 WCHAR *endptr_leftOp, *endptr_rightOp;
2756 long int leftOperand_int, rightOperand_int;
2757 BOOL int_operands;
2758 static const WCHAR lssW[] = {'l','s','s','\0'};
2759 static const WCHAR leqW[] = {'l','e','q','\0'};
2760 static const WCHAR equW[] = {'e','q','u','\0'};
2761 static const WCHAR neqW[] = {'n','e','q','\0'};
2762 static const WCHAR geqW[] = {'g','e','q','\0'};
2763 static const WCHAR gtrW[] = {'g','t','r','\0'};
2765 /* == is a special case, as it always compares strings */
2766 if (!lstrcmpiW(operator, eqeqW))
2767 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2768 : lstrcmpW (leftOperand, rightOperand) == 0;
2770 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2771 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2772 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2773 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2775 /* Perform actual (integer or string) comparison */
2776 if (!lstrcmpiW(operator, lssW)) {
2777 if (int_operands)
2778 return leftOperand_int < rightOperand_int;
2779 else
2780 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2781 : lstrcmpW (leftOperand, rightOperand) < 0;
2784 if (!lstrcmpiW(operator, leqW)) {
2785 if (int_operands)
2786 return leftOperand_int <= rightOperand_int;
2787 else
2788 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2789 : lstrcmpW (leftOperand, rightOperand) <= 0;
2792 if (!lstrcmpiW(operator, equW)) {
2793 if (int_operands)
2794 return leftOperand_int == rightOperand_int;
2795 else
2796 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2797 : lstrcmpW (leftOperand, rightOperand) == 0;
2800 if (!lstrcmpiW(operator, neqW)) {
2801 if (int_operands)
2802 return leftOperand_int != rightOperand_int;
2803 else
2804 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2805 : lstrcmpW (leftOperand, rightOperand) != 0;
2808 if (!lstrcmpiW(operator, geqW)) {
2809 if (int_operands)
2810 return leftOperand_int >= rightOperand_int;
2811 else
2812 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2813 : lstrcmpW (leftOperand, rightOperand) >= 0;
2816 if (!lstrcmpiW(operator, gtrW)) {
2817 if (int_operands)
2818 return leftOperand_int > rightOperand_int;
2819 else
2820 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2821 : lstrcmpW (leftOperand, rightOperand) > 0;
2824 return -1;
2827 /****************************************************************************
2828 * WCMD_if
2830 * Batch file conditional.
2832 * On entry, cmdlist will point to command containing the IF, and optionally
2833 * the first command to execute (if brackets not found)
2834 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2835 * If ('s were found, execute all within that bracket
2836 * Command may optionally be followed by an ELSE - need to skip instructions
2837 * in the else using the same logic
2839 * FIXME: Much more syntax checking needed!
2841 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2843 int negate; /* Negate condition */
2844 int test; /* Condition evaluation result */
2845 WCHAR condition[MAX_PATH], *command;
2846 static const WCHAR notW[] = {'n','o','t','\0'};
2847 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2848 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2849 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2850 static const WCHAR parmI[] = {'/','I','\0'};
2851 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2853 negate = !lstrcmpiW(param1,notW);
2854 strcpyW(condition, (negate ? param2 : param1));
2855 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2857 if (!lstrcmpiW (condition, errlvlW)) {
2858 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2859 WCHAR *endptr;
2860 long int param_int = strtolW(param, &endptr, 10);
2861 if (*endptr) goto syntax_err;
2862 test = ((long int)errorlevel >= param_int);
2863 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2865 else if (!lstrcmpiW (condition, existW)) {
2866 WIN32_FIND_DATAW fd;
2867 HANDLE hff = FindFirstFileW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE), &fd);
2868 test = (hff != INVALID_HANDLE_VALUE );
2869 if (test) FindClose(hff);
2871 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2873 else if (!lstrcmpiW (condition, defdW)) {
2874 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2875 NULL, 0) > 0);
2876 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2878 else { /* comparison operation */
2879 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2880 WCHAR *paramStart;
2882 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, &paramStart, TRUE, FALSE));
2883 if (!*leftOperand)
2884 goto syntax_err;
2886 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2887 p = paramStart + strlenW(leftOperand);
2888 while (*p == ' ' || *p == '\t')
2889 p++;
2891 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2892 strcpyW(operator, eqeqW);
2893 else {
2894 strcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2895 if (!*operator) goto syntax_err;
2897 p += strlenW(operator);
2899 strcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2900 if (!*rightOperand)
2901 goto syntax_err;
2903 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2904 if (test == -1)
2905 goto syntax_err;
2907 p = paramStart + strlenW(rightOperand);
2908 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2911 /* Process rest of IF statement which is on the same line
2912 Note: This may process all or some of the cmdList (eg a GOTO) */
2913 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2914 return;
2916 syntax_err:
2917 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2920 /****************************************************************************
2921 * WCMD_move
2923 * Move a file, directory tree or wildcarded set of files.
2926 void WCMD_move (void)
2928 BOOL status;
2929 WIN32_FIND_DATAW fd;
2930 HANDLE hff;
2931 WCHAR input[MAX_PATH];
2932 WCHAR output[MAX_PATH];
2933 WCHAR drive[10];
2934 WCHAR dir[MAX_PATH];
2935 WCHAR fname[MAX_PATH];
2936 WCHAR ext[MAX_PATH];
2938 if (param1[0] == 0x00) {
2939 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2940 return;
2943 /* If no destination supplied, assume current directory */
2944 if (param2[0] == 0x00) {
2945 strcpyW(param2, dotW);
2948 /* If 2nd parm is directory, then use original filename */
2949 /* Convert partial path to full path */
2950 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2951 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2952 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2953 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2955 /* Split into components */
2956 WCMD_splitpath(input, drive, dir, fname, ext);
2958 hff = FindFirstFileW(input, &fd);
2959 if (hff == INVALID_HANDLE_VALUE)
2960 return;
2962 do {
2963 WCHAR dest[MAX_PATH];
2964 WCHAR src[MAX_PATH];
2965 DWORD attribs;
2966 BOOL ok = TRUE;
2968 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2970 /* Build src & dest name */
2971 strcpyW(src, drive);
2972 strcatW(src, dir);
2974 /* See if dest is an existing directory */
2975 attribs = GetFileAttributesW(output);
2976 if (attribs != INVALID_FILE_ATTRIBUTES &&
2977 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2978 strcpyW(dest, output);
2979 strcatW(dest, slashW);
2980 strcatW(dest, fd.cFileName);
2981 } else {
2982 strcpyW(dest, output);
2985 strcatW(src, fd.cFileName);
2987 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2988 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2990 /* If destination exists, prompt unless /Y supplied */
2991 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2992 BOOL force = FALSE;
2993 WCHAR copycmd[MAXSTRING];
2994 DWORD len;
2996 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2997 if (strstrW (quals, parmNoY))
2998 force = FALSE;
2999 else if (strstrW (quals, parmY))
3000 force = TRUE;
3001 else {
3002 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
3003 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
3004 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
3005 && ! lstrcmpiW (copycmd, parmY));
3008 /* Prompt if overwriting */
3009 if (!force) {
3010 WCHAR* question;
3012 /* Ask for confirmation */
3013 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
3014 ok = WCMD_ask_confirm(question, FALSE, NULL);
3015 LocalFree(question);
3017 /* So delete the destination prior to the move */
3018 if (ok) {
3019 if (!DeleteFileW(dest)) {
3020 WCMD_print_error ();
3021 errorlevel = 1;
3022 ok = FALSE;
3028 if (ok) {
3029 status = MoveFileW(src, dest);
3030 } else {
3031 status = TRUE;
3034 if (!status) {
3035 WCMD_print_error ();
3036 errorlevel = 1;
3038 } while (FindNextFileW(hff, &fd) != 0);
3040 FindClose(hff);
3043 /****************************************************************************
3044 * WCMD_pause
3046 * Suspend execution of a batch script until a key is typed
3049 void WCMD_pause (void)
3051 DWORD oldmode;
3052 BOOL have_console;
3053 DWORD count;
3054 WCHAR key;
3055 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
3057 have_console = GetConsoleMode(hIn, &oldmode);
3058 if (have_console)
3059 SetConsoleMode(hIn, 0);
3061 WCMD_output_asis(anykey);
3062 WCMD_ReadFile(hIn, &key, 1, &count);
3063 if (have_console)
3064 SetConsoleMode(hIn, oldmode);
3067 /****************************************************************************
3068 * WCMD_remove_dir
3070 * Delete a directory.
3073 void WCMD_remove_dir (WCHAR *args) {
3075 int argno = 0;
3076 int argsProcessed = 0;
3077 WCHAR *argN = args;
3078 static const WCHAR parmS[] = {'/','S','\0'};
3079 static const WCHAR parmQ[] = {'/','Q','\0'};
3081 /* Loop through all args */
3082 while (argN) {
3083 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3084 if (argN && argN[0] != '/') {
3085 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
3086 wine_dbgstr_w(quals));
3087 argsProcessed++;
3089 /* If subdirectory search not supplied, just try to remove
3090 and report error if it fails (eg if it contains a file) */
3091 if (strstrW (quals, parmS) == NULL) {
3092 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
3094 /* Otherwise use ShFileOp to recursively remove a directory */
3095 } else {
3097 SHFILEOPSTRUCTW lpDir;
3099 /* Ask first */
3100 if (strstrW (quals, parmQ) == NULL) {
3101 BOOL ok;
3102 WCHAR question[MAXSTRING];
3103 static const WCHAR fmt[] = {'%','s',' ','\0'};
3105 /* Ask for confirmation */
3106 wsprintfW(question, fmt, thisArg);
3107 ok = WCMD_ask_confirm(question, TRUE, NULL);
3109 /* Abort if answer is 'N' */
3110 if (!ok) return;
3113 /* Do the delete */
3114 lpDir.hwnd = NULL;
3115 lpDir.pTo = NULL;
3116 lpDir.pFrom = thisArg;
3117 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
3118 lpDir.wFunc = FO_DELETE;
3120 /* SHFileOperationW needs file list with a double null termination */
3121 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3123 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3128 /* Handle no valid args */
3129 if (argsProcessed == 0) {
3130 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3131 return;
3136 /****************************************************************************
3137 * WCMD_rename
3139 * Rename a file.
3142 void WCMD_rename (void)
3144 BOOL status;
3145 HANDLE hff;
3146 WIN32_FIND_DATAW fd;
3147 WCHAR input[MAX_PATH];
3148 WCHAR *dotDst = NULL;
3149 WCHAR drive[10];
3150 WCHAR dir[MAX_PATH];
3151 WCHAR fname[MAX_PATH];
3152 WCHAR ext[MAX_PATH];
3154 errorlevel = 0;
3156 /* Must be at least two args */
3157 if (param1[0] == 0x00 || param2[0] == 0x00) {
3158 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3159 errorlevel = 1;
3160 return;
3163 /* Destination cannot contain a drive letter or directory separator */
3164 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
3165 SetLastError(ERROR_INVALID_PARAMETER);
3166 WCMD_print_error();
3167 errorlevel = 1;
3168 return;
3171 /* Convert partial path to full path */
3172 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
3173 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3174 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3175 dotDst = strchrW(param2, '.');
3177 /* Split into components */
3178 WCMD_splitpath(input, drive, dir, fname, ext);
3180 hff = FindFirstFileW(input, &fd);
3181 if (hff == INVALID_HANDLE_VALUE)
3182 return;
3184 do {
3185 WCHAR dest[MAX_PATH];
3186 WCHAR src[MAX_PATH];
3187 WCHAR *dotSrc = NULL;
3188 int dirLen;
3190 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3192 /* FIXME: If dest name or extension is *, replace with filename/ext
3193 part otherwise use supplied name. This supports:
3194 ren *.fred *.jim
3195 ren jim.* fred.* etc
3196 However, windows has a more complex algorithm supporting eg
3197 ?'s and *'s mid name */
3198 dotSrc = strchrW(fd.cFileName, '.');
3200 /* Build src & dest name */
3201 strcpyW(src, drive);
3202 strcatW(src, dir);
3203 strcpyW(dest, src);
3204 dirLen = strlenW(src);
3205 strcatW(src, fd.cFileName);
3207 /* Build name */
3208 if (param2[0] == '*') {
3209 strcatW(dest, fd.cFileName);
3210 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3211 } else {
3212 strcatW(dest, param2);
3213 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3216 /* Build Extension */
3217 if (dotDst && (*(dotDst+1)=='*')) {
3218 if (dotSrc) strcatW(dest, dotSrc);
3219 } else if (dotDst) {
3220 strcatW(dest, dotDst);
3223 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3224 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3226 status = MoveFileW(src, dest);
3228 if (!status) {
3229 WCMD_print_error ();
3230 errorlevel = 1;
3232 } while (FindNextFileW(hff, &fd) != 0);
3234 FindClose(hff);
3237 /*****************************************************************************
3238 * WCMD_dupenv
3240 * Make a copy of the environment.
3242 static WCHAR *WCMD_dupenv( const WCHAR *env )
3244 WCHAR *env_copy;
3245 int len;
3247 if( !env )
3248 return NULL;
3250 len = 0;
3251 while ( env[len] )
3252 len += (strlenW(&env[len]) + 1);
3254 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3255 if (!env_copy)
3257 WINE_ERR("out of memory\n");
3258 return env_copy;
3260 memcpy (env_copy, env, len*sizeof (WCHAR));
3261 env_copy[len] = 0;
3263 return env_copy;
3266 /*****************************************************************************
3267 * WCMD_setlocal
3269 * setlocal pushes the environment onto a stack
3270 * Save the environment as unicode so we don't screw anything up.
3272 void WCMD_setlocal (const WCHAR *s) {
3273 WCHAR *env;
3274 struct env_stack *env_copy;
3275 WCHAR cwd[MAX_PATH];
3276 BOOL newdelay;
3277 static const WCHAR ondelayW[] = {'E','N','A','B','L','E','D','E','L','A',
3278 'Y','E','D','E','X','P','A','N','S','I',
3279 'O','N','\0'};
3280 static const WCHAR offdelayW[] = {'D','I','S','A','B','L','E','D','E','L',
3281 'A','Y','E','D','E','X','P','A','N','S',
3282 'I','O','N','\0'};
3284 /* setlocal does nothing outside of batch programs */
3285 if (!context) return;
3287 /* DISABLEEXTENSIONS ignored */
3289 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3290 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3291 if (!strcmpiW(param1, ondelayW) || !strcmpiW(param2, ondelayW)) {
3292 newdelay = TRUE;
3293 } else if (!strcmpiW(param1, offdelayW) || !strcmpiW(param2, offdelayW)) {
3294 newdelay = FALSE;
3295 } else {
3296 newdelay = delayedsubst;
3298 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3300 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3301 if( !env_copy )
3303 WINE_ERR ("out of memory\n");
3304 return;
3307 env = GetEnvironmentStringsW ();
3308 env_copy->strings = WCMD_dupenv (env);
3309 if (env_copy->strings)
3311 env_copy->batchhandle = context->h;
3312 env_copy->next = saved_environment;
3313 env_copy->delayedsubst = delayedsubst;
3314 delayedsubst = newdelay;
3315 saved_environment = env_copy;
3317 /* Save the current drive letter */
3318 GetCurrentDirectoryW(MAX_PATH, cwd);
3319 env_copy->u.cwd = cwd[0];
3321 else
3322 LocalFree (env_copy);
3324 FreeEnvironmentStringsW (env);
3328 /*****************************************************************************
3329 * WCMD_endlocal
3331 * endlocal pops the environment off a stack
3332 * Note: When searching for '=', search from WCHAR position 1, to handle
3333 * special internal environment variables =C:, =D: etc
3335 void WCMD_endlocal (void) {
3336 WCHAR *env, *old, *p;
3337 struct env_stack *temp;
3338 int len, n;
3340 /* setlocal does nothing outside of batch programs */
3341 if (!context) return;
3343 /* setlocal needs a saved environment from within the same context (batch
3344 program) as it was saved in */
3345 if (!saved_environment || saved_environment->batchhandle != context->h)
3346 return;
3348 /* pop the old environment from the stack */
3349 temp = saved_environment;
3350 saved_environment = temp->next;
3352 /* delete the current environment, totally */
3353 env = GetEnvironmentStringsW ();
3354 old = WCMD_dupenv (env);
3355 len = 0;
3356 while (old[len]) {
3357 n = strlenW(&old[len]) + 1;
3358 p = strchrW(&old[len] + 1, '=');
3359 if (p)
3361 *p++ = 0;
3362 SetEnvironmentVariableW (&old[len], NULL);
3364 len += n;
3366 LocalFree (old);
3367 FreeEnvironmentStringsW (env);
3369 /* restore old environment */
3370 env = temp->strings;
3371 len = 0;
3372 delayedsubst = temp->delayedsubst;
3373 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3374 while (env[len]) {
3375 n = strlenW(&env[len]) + 1;
3376 p = strchrW(&env[len] + 1, '=');
3377 if (p)
3379 *p++ = 0;
3380 SetEnvironmentVariableW (&env[len], p);
3382 len += n;
3385 /* Restore current drive letter */
3386 if (IsCharAlphaW(temp->u.cwd)) {
3387 WCHAR envvar[4];
3388 WCHAR cwd[MAX_PATH];
3389 static const WCHAR fmt[] = {'=','%','c',':','\0'};
3391 wsprintfW(envvar, fmt, temp->u.cwd);
3392 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3393 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3394 SetCurrentDirectoryW(cwd);
3398 LocalFree (env);
3399 LocalFree (temp);
3402 /*****************************************************************************
3403 * WCMD_setshow_default
3405 * Set/Show the current default directory
3408 void WCMD_setshow_default (const WCHAR *args) {
3410 BOOL status;
3411 WCHAR string[1024];
3412 WCHAR cwd[1024];
3413 WCHAR *pos;
3414 WIN32_FIND_DATAW fd;
3415 HANDLE hff;
3416 static const WCHAR parmD[] = {'/','D','\0'};
3418 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3420 /* Skip /D and trailing whitespace if on the front of the command line */
3421 if (strlenW(args) >= 2 &&
3422 CompareStringW(LOCALE_USER_DEFAULT,
3423 NORM_IGNORECASE | SORT_STRINGSORT,
3424 args, 2, parmD, -1) == CSTR_EQUAL) {
3425 args += 2;
3426 while (*args && (*args==' ' || *args=='\t'))
3427 args++;
3430 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3431 if (strlenW(args) == 0) {
3432 strcatW (cwd, newlineW);
3433 WCMD_output_asis (cwd);
3435 else {
3436 /* Remove any double quotes, which may be in the
3437 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3438 pos = string;
3439 while (*args) {
3440 if (*args != '"') *pos++ = *args;
3441 args++;
3443 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3444 pos--;
3445 *pos = 0x00;
3447 /* Search for appropriate directory */
3448 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3449 hff = FindFirstFileW(string, &fd);
3450 if (hff != INVALID_HANDLE_VALUE) {
3451 do {
3452 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3453 WCHAR fpath[MAX_PATH];
3454 WCHAR drive[10];
3455 WCHAR dir[MAX_PATH];
3456 WCHAR fname[MAX_PATH];
3457 WCHAR ext[MAX_PATH];
3458 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3460 /* Convert path into actual directory spec */
3461 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3462 WCMD_splitpath(fpath, drive, dir, fname, ext);
3464 /* Rebuild path */
3465 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3466 break;
3468 } while (FindNextFileW(hff, &fd) != 0);
3469 FindClose(hff);
3472 /* Change to that directory */
3473 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3475 status = SetCurrentDirectoryW(string);
3476 if (!status) {
3477 errorlevel = 1;
3478 WCMD_print_error ();
3479 return;
3480 } else {
3482 /* Save away the actual new directory, to store as current location */
3483 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3485 /* Restore old directory if drive letter would change, and
3486 CD x:\directory /D (or pushd c:\directory) not supplied */
3487 if ((strstrW(quals, parmD) == NULL) &&
3488 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3489 SetCurrentDirectoryW(cwd);
3493 /* Set special =C: type environment variable, for drive letter of
3494 change of directory, even if path was restored due to missing
3495 /D (allows changing drive letter when not resident on that
3496 drive */
3497 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3498 WCHAR env[4];
3499 strcpyW(env, equalW);
3500 memcpy(env+1, string, 2 * sizeof(WCHAR));
3501 env[3] = 0x00;
3502 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3503 SetEnvironmentVariableW(env, string);
3507 return;
3510 /****************************************************************************
3511 * WCMD_setshow_date
3513 * Set/Show the system date
3514 * FIXME: Can't change date yet
3517 void WCMD_setshow_date (void) {
3519 WCHAR curdate[64], buffer[64];
3520 DWORD count;
3521 static const WCHAR parmT[] = {'/','T','\0'};
3523 if (strlenW(param1) == 0) {
3524 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3525 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3526 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3527 if (strstrW (quals, parmT) == NULL) {
3528 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3529 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3530 if (count > 2) {
3531 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3535 else WCMD_print_error ();
3537 else {
3538 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3542 /****************************************************************************
3543 * WCMD_compare
3544 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3545 * the equals sign.
3547 static int WCMD_compare( const void *a, const void *b )
3549 int r;
3550 const WCHAR * const *str_a = a, * const *str_b = b;
3551 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3552 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3553 if( r == CSTR_LESS_THAN ) return -1;
3554 if( r == CSTR_GREATER_THAN ) return 1;
3555 return 0;
3558 /****************************************************************************
3559 * WCMD_setshow_sortenv
3561 * sort variables into order for display
3562 * Optionally only display those who start with a stub
3563 * returns the count displayed
3565 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3567 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3568 const WCHAR **str;
3570 if (stub) stublen = strlenW(stub);
3572 /* count the number of strings, and the total length */
3573 while ( s[len] ) {
3574 len += (strlenW(&s[len]) + 1);
3575 count++;
3578 /* add the strings to an array */
3579 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3580 if( !str )
3581 return 0;
3582 str[0] = s;
3583 for( i=1; i<count; i++ )
3584 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3586 /* sort the array */
3587 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3589 /* print it */
3590 for( i=0; i<count; i++ ) {
3591 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3592 NORM_IGNORECASE | SORT_STRINGSORT,
3593 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3594 /* Don't display special internal variables */
3595 if (str[i][0] != '=') {
3596 WCMD_output_asis(str[i]);
3597 WCMD_output_asis(newlineW);
3598 displayedcount++;
3603 LocalFree( str );
3604 return displayedcount;
3607 /****************************************************************************
3608 * WCMD_getprecedence
3609 * Return the precedence of a particular operator
3611 static int WCMD_getprecedence(const WCHAR in)
3613 switch (in) {
3614 case '!':
3615 case '~':
3616 case OP_POSITIVE:
3617 case OP_NEGATIVE:
3618 return 8;
3619 case '*':
3620 case '/':
3621 case '%':
3622 return 7;
3623 case '+':
3624 case '-':
3625 return 6;
3626 case '<':
3627 case '>':
3628 return 5;
3629 case '&':
3630 return 4;
3631 case '^':
3632 return 3;
3633 case '|':
3634 return 2;
3635 case '=':
3636 case OP_ASSSIGNMUL:
3637 case OP_ASSSIGNDIV:
3638 case OP_ASSSIGNMOD:
3639 case OP_ASSSIGNADD:
3640 case OP_ASSSIGNSUB:
3641 case OP_ASSSIGNAND:
3642 case OP_ASSSIGNNOT:
3643 case OP_ASSSIGNOR:
3644 case OP_ASSSIGNSHL:
3645 case OP_ASSSIGNSHR:
3646 return 1;
3647 default:
3648 return 0;
3652 /****************************************************************************
3653 * WCMD_pushnumber
3654 * Push either a number or name (environment variable) onto the supplied
3655 * stack
3657 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3658 VARSTACK *thisstack = heap_alloc(sizeof(VARSTACK));
3659 thisstack->isnum = (var == NULL);
3660 if (var) {
3661 thisstack->variable = var;
3662 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3663 } else {
3664 thisstack->value = num;
3665 WINE_TRACE("Pushed number %d\n", num);
3667 thisstack->next = *varstack;
3668 *varstack = thisstack;
3671 /****************************************************************************
3672 * WCMD_peeknumber
3673 * Returns the value of the top number or environment variable on the stack
3674 * and leaves the item on the stack.
3676 static int WCMD_peeknumber(VARSTACK **varstack) {
3677 int result = 0;
3678 VARSTACK *thisvar;
3680 if (varstack) {
3681 thisvar = *varstack;
3682 if (!thisvar->isnum) {
3683 WCHAR tmpstr[MAXSTRING];
3684 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3685 result = strtoulW(tmpstr,NULL,0);
3687 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3688 } else {
3689 result = thisvar->value;
3692 WINE_TRACE("Peeked number %d\n", result);
3693 return result;
3696 /****************************************************************************
3697 * WCMD_popnumber
3698 * Returns the value of the top number or environment variable on the stack
3699 * and removes the item from the stack.
3701 static int WCMD_popnumber(VARSTACK **varstack) {
3702 int result = 0;
3703 VARSTACK *thisvar;
3705 if (varstack) {
3706 thisvar = *varstack;
3707 result = WCMD_peeknumber(varstack);
3708 if (!thisvar->isnum) heap_free(thisvar->variable);
3709 *varstack = thisvar->next;
3710 heap_free(thisvar);
3712 WINE_TRACE("Popped number %d\n", result);
3713 return result;
3716 /****************************************************************************
3717 * WCMD_pushoperator
3718 * Push an operator onto the supplied stack
3720 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3721 OPSTACK *thisstack = heap_alloc(sizeof(OPSTACK));
3722 thisstack->precedence = precedence;
3723 thisstack->op = op;
3724 thisstack->next = *opstack;
3725 WINE_TRACE("Pushed operator %c\n", op);
3726 *opstack = thisstack;
3729 /****************************************************************************
3730 * WCMD_popoperator
3731 * Returns the operator from the top of the stack and removes the item from
3732 * the stack.
3734 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3735 WCHAR result = 0;
3736 OPSTACK *thisop;
3738 if (opstack) {
3739 thisop = *opstack;
3740 result = thisop->op;
3741 *opstack = thisop->next;
3742 heap_free(thisop);
3744 WINE_TRACE("Popped operator %c\n", result);
3745 return result;
3748 /****************************************************************************
3749 * WCMD_reduce
3750 * Actions the top operator on the stack against the first and sometimes
3751 * second value on the variable stack, and pushes the result
3752 * Returns non-zero on error.
3754 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3755 WCHAR thisop;
3756 int var1,var2;
3757 int rc = 0;
3759 if (!*opstack || !*varstack) {
3760 WINE_TRACE("No operators for the reduce\n");
3761 return WCMD_NOOPERATOR;
3764 /* Remove the top operator */
3765 thisop = WCMD_popoperator(opstack);
3766 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
3768 /* One variable operators */
3769 var1 = WCMD_popnumber(varstack);
3770 switch (thisop) {
3771 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3772 break;
3773 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3774 break;
3775 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3776 break;
3777 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3778 break;
3781 /* Two variable operators */
3782 if (!*varstack) {
3783 WINE_TRACE("No operands left for the reduce?\n");
3784 return WCMD_NOOPERAND;
3786 switch (thisop) {
3787 case '!':
3788 case '~':
3789 case OP_POSITIVE:
3790 case OP_NEGATIVE:
3791 break; /* Handled above */
3792 case '*': var2 = WCMD_popnumber(varstack);
3793 WCMD_pushnumber(NULL, var2*var1, varstack);
3794 break;
3795 case '/': var2 = WCMD_popnumber(varstack);
3796 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3797 WCMD_pushnumber(NULL, var2/var1, varstack);
3798 break;
3799 case '+': var2 = WCMD_popnumber(varstack);
3800 WCMD_pushnumber(NULL, var2+var1, varstack);
3801 break;
3802 case '-': var2 = WCMD_popnumber(varstack);
3803 WCMD_pushnumber(NULL, var2-var1, varstack);
3804 break;
3805 case '&': var2 = WCMD_popnumber(varstack);
3806 WCMD_pushnumber(NULL, var2&var1, varstack);
3807 break;
3808 case '%': var2 = WCMD_popnumber(varstack);
3809 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3810 WCMD_pushnumber(NULL, var2%var1, varstack);
3811 break;
3812 case '^': var2 = WCMD_popnumber(varstack);
3813 WCMD_pushnumber(NULL, var2^var1, varstack);
3814 break;
3815 case '<': var2 = WCMD_popnumber(varstack);
3816 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3817 which differs from the compiler (for example gcc) so being explicit. */
3818 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3819 WCMD_pushnumber(NULL, 0, varstack);
3820 } else {
3821 WCMD_pushnumber(NULL, var2<<var1, varstack);
3823 break;
3824 case '>': var2 = WCMD_popnumber(varstack);
3825 WCMD_pushnumber(NULL, var2>>var1, varstack);
3826 break;
3827 case '|': var2 = WCMD_popnumber(varstack);
3828 WCMD_pushnumber(NULL, var2|var1, varstack);
3829 break;
3831 case OP_ASSSIGNMUL:
3832 case OP_ASSSIGNDIV:
3833 case OP_ASSSIGNMOD:
3834 case OP_ASSSIGNADD:
3835 case OP_ASSSIGNSUB:
3836 case OP_ASSSIGNAND:
3837 case OP_ASSSIGNNOT:
3838 case OP_ASSSIGNOR:
3839 case OP_ASSSIGNSHL:
3840 case OP_ASSSIGNSHR:
3842 int i = 0;
3844 /* The left of an equals must be one variable */
3845 if (!(*varstack) || (*varstack)->isnum) {
3846 return WCMD_NOOPERAND;
3849 /* Make the number stack grow by inserting the value of the variable */
3850 var2 = WCMD_peeknumber(varstack);
3851 WCMD_pushnumber(NULL, var2, varstack);
3852 WCMD_pushnumber(NULL, var1, varstack);
3854 /* Make the operand stack grow by pushing the assign operator plus the
3855 operator to perform */
3856 while (calcassignments[i].op != ' ' &&
3857 calcassignments[i].calculatedop != thisop) {
3858 i++;
3860 if (calcassignments[i].calculatedop == ' ') {
3861 WINE_ERR("Unexpected operator %c\n", thisop);
3862 return WCMD_NOOPERATOR;
3864 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3865 WCMD_pushoperator(calcassignments[i].op,
3866 WCMD_getprecedence(calcassignments[i].op), opstack);
3867 break;
3870 case '=':
3872 WCHAR intFormat[] = {'%','d','\0'};
3873 WCHAR result[MAXSTRING];
3875 /* Build the result, then push it onto the stack */
3876 sprintfW(result, intFormat, var1);
3877 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3878 wine_dbgstr_w(result));
3879 SetEnvironmentVariableW((*varstack)->variable, result);
3880 var2 = WCMD_popnumber(varstack);
3881 WCMD_pushnumber(NULL, var1, varstack);
3882 break;
3885 default: WINE_ERR("Unrecognized operator %c\n", thisop);
3888 return rc;
3892 /****************************************************************************
3893 * WCMD_handleExpression
3894 * Handles an expression provided to set /a - If it finds brackets, it uses
3895 * recursion to process the parts in brackets.
3897 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3899 static const WCHAR mathDelims[] = {' ','\t','(',')','!','~','-','*','/','%',
3900 '+','<','>','&','^','|','=',',','\0' };
3901 int rc = 0;
3902 WCHAR *pos;
3903 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3904 OPSTACK *opstackhead = NULL;
3905 VARSTACK *varstackhead = NULL;
3906 WCHAR foundhalf = 0;
3908 /* Initialize */
3909 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3910 pos = *expr;
3912 /* Iterate through until whole expression is processed */
3913 while (pos && *pos) {
3914 BOOL treatasnumber;
3916 /* Skip whitespace to get to the next character to process*/
3917 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3918 if (!*pos) goto exprreturn;
3920 /* If we have found anything other than an operator then it's a number/variable */
3921 if (strchrW(mathDelims, *pos) == NULL) {
3922 WCHAR *parmstart, *parm, *dupparm;
3923 WCHAR *nextpos;
3925 /* Cannot have an expression with var/number twice, without an operator
3926 in-between, nor or number following a half constructed << or >> operator */
3927 if (lastwasnumber || foundhalf) {
3928 rc = WCMD_NOOPERATOR;
3929 goto exprerrorreturn;
3931 lastwasnumber = TRUE;
3933 if (isdigitW(*pos)) {
3934 /* For a number - just push it onto the stack */
3935 int num = strtoulW(pos, &nextpos, 0);
3936 WCMD_pushnumber(NULL, num, &varstackhead);
3937 pos = nextpos;
3939 /* Verify the number was validly formed */
3940 if (*nextpos && (strchrW(mathDelims, *nextpos) == NULL)) {
3941 rc = WCMD_BADHEXOCT;
3942 goto exprerrorreturn;
3944 } else {
3946 /* For a variable - just push it onto the stack */
3947 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3948 dupparm = heap_strdupW(parm);
3949 WCMD_pushnumber(dupparm, 0, &varstackhead);
3950 pos = parmstart + strlenW(dupparm);
3952 continue;
3955 /* We have found an operator. Some operators are one character, some two, and the minus
3956 and plus signs need special processing as they can be either operators or just influence
3957 the parameter which follows them */
3958 if (foundhalf && (*pos != foundhalf)) {
3959 /* Badly constructed operator pair */
3960 rc = WCMD_NOOPERATOR;
3961 goto exprerrorreturn;
3964 treatasnumber = FALSE; /* We are processing an operand */
3965 switch (*pos) {
3967 /* > and < are special as they are double character operators (and spaces can be between them!)
3968 If we see these for the first time, set a flag, and second time around we continue.
3969 Note these double character operators are stored as just one of the characters on the stack */
3970 case '>':
3971 case '<': if (!foundhalf) {
3972 foundhalf = *pos;
3973 pos++;
3974 break;
3976 /* We have found the rest, so clear up the knowledge of the half completed part and
3977 drop through to normal operator processing */
3978 foundhalf = 0;
3979 /* drop through */
3981 case '=': if (*pos=='=') {
3982 /* = is special cased as if the last was an operator then we may have e.g. += or
3983 *= etc which we need to handle by replacing the operator that is on the stack
3984 with a calculated assignment equivalent */
3985 if (!lastwasnumber && opstackhead) {
3986 int i = 0;
3987 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3988 i++;
3990 if (calcassignments[i].op == ' ') {
3991 rc = WCMD_NOOPERAND;
3992 goto exprerrorreturn;
3993 } else {
3994 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3995 when the general operator handling happens further down. */
3996 *pos = calcassignments[i].calculatedop;
3997 WCMD_popoperator(&opstackhead);
4001 /* Drop though */
4003 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
4004 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
4005 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
4006 /* drop through */
4007 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
4008 /* drop through */
4010 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
4011 case '!': /* drop through */
4012 case '~': /* drop through */
4013 case '/': /* drop through */
4014 case '%': /* drop through */
4015 case '&': /* drop through */
4016 case '^': /* drop through */
4017 case '*': /* drop through */
4018 case '|':
4019 /* General code for handling most of the operators - look at the
4020 precedence of the top item on the stack, and see if we need to
4021 action the stack before we push something else onto it. */
4023 int precedence = WCMD_getprecedence(*pos);
4024 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
4025 precedence, !opstackhead?-1:opstackhead->precedence);
4027 /* In general, for things with the same precedence, reduce immediately
4028 except for assignments and unary operators which do not */
4029 while (!rc && opstackhead &&
4030 ((opstackhead->precedence > precedence) ||
4031 ((opstackhead->precedence == precedence) &&
4032 (precedence != 1) && (precedence != 8)))) {
4033 rc = WCMD_reduce(&opstackhead, &varstackhead);
4035 if (rc) goto exprerrorreturn;
4036 WCMD_pushoperator(*pos, precedence, &opstackhead);
4037 pos++;
4038 break;
4041 /* comma means start a new expression, ie calculate what we have */
4042 case ',':
4044 int prevresult = -1;
4045 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
4046 while (!rc && opstackhead) {
4047 rc = WCMD_reduce(&opstackhead, &varstackhead);
4049 if (rc) goto exprerrorreturn;
4050 /* If we have anything other than one number left, error
4051 otherwise throw the number away */
4052 if (!varstackhead || varstackhead->next) {
4053 rc = WCMD_NOOPERATOR;
4054 goto exprerrorreturn;
4056 prevresult = WCMD_popnumber(&varstackhead);
4057 WINE_TRACE("Expression resolved to %d\n", prevresult);
4058 heap_free(varstackhead);
4059 varstackhead = NULL;
4060 pos++;
4061 break;
4064 /* Open bracket - use iteration to parse the inner expression, then continue */
4065 case '(' : {
4066 int exprresult = 0;
4067 pos++;
4068 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
4069 if (rc) goto exprerrorreturn;
4070 WCMD_pushnumber(NULL, exprresult, &varstackhead);
4071 break;
4074 /* Close bracket - we have finished this depth, calculate and return */
4075 case ')' : {
4076 pos++;
4077 treatasnumber = TRUE; /* Things in brackets result in a number */
4078 if (depth == 0) {
4079 rc = WCMD_BADPAREN;
4080 goto exprerrorreturn;
4082 goto exprreturn;
4085 default:
4086 WINE_ERR("Unrecognized operator %c\n", *pos);
4087 pos++;
4089 lastwasnumber = treatasnumber;
4092 exprreturn:
4093 *expr = pos;
4095 /* We need to reduce until we have a single number (or variable) on the
4096 stack and set the return value to that */
4097 while (!rc && opstackhead) {
4098 rc = WCMD_reduce(&opstackhead, &varstackhead);
4100 if (rc) goto exprerrorreturn;
4102 /* If we have anything other than one number left, error
4103 otherwise throw the number away */
4104 if (!varstackhead || varstackhead->next) {
4105 rc = WCMD_NOOPERATOR;
4106 goto exprerrorreturn;
4109 /* Now get the number (and convert if it's just a variable name) */
4110 *ret = WCMD_popnumber(&varstackhead);
4112 exprerrorreturn:
4113 /* Free all remaining memory */
4114 while (opstackhead) WCMD_popoperator(&opstackhead);
4115 while (varstackhead) WCMD_popnumber(&varstackhead);
4117 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
4118 return rc;
4121 /****************************************************************************
4122 * WCMD_setshow_env
4124 * Set/Show the environment variables
4127 void WCMD_setshow_env (WCHAR *s) {
4129 LPVOID env;
4130 WCHAR *p;
4131 BOOL status;
4132 static const WCHAR parmP[] = {'/','P','\0'};
4133 static const WCHAR parmA[] = {'/','A','\0'};
4134 WCHAR string[MAXSTRING];
4136 if (param1[0] == 0x00 && quals[0] == 0x00) {
4137 env = GetEnvironmentStringsW();
4138 WCMD_setshow_sortenv( env, NULL );
4139 return;
4142 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4143 if (CompareStringW(LOCALE_USER_DEFAULT,
4144 NORM_IGNORECASE | SORT_STRINGSORT,
4145 s, 2, parmP, -1) == CSTR_EQUAL) {
4146 DWORD count;
4148 s += 2;
4149 while (*s && (*s==' ' || *s=='\t')) s++;
4150 /* set /P "var=value"jim ignores anything after the last quote */
4151 if (*s=='\"') {
4152 WCHAR *lastquote;
4153 lastquote = WCMD_strip_quotes(s);
4154 if (lastquote) *lastquote = 0x00;
4155 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4158 /* If no parameter, or no '=' sign, return an error */
4159 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
4160 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4161 return;
4164 /* Output the prompt */
4165 *p++ = '\0';
4166 if (strlenW(p) != 0) WCMD_output_asis(p);
4168 /* Read the reply */
4169 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4170 if (count > 1) {
4171 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4172 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4173 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4174 wine_dbgstr_w(string));
4175 SetEnvironmentVariableW(s, string);
4178 /* See if /A supplied, and if so calculate the results of all the expressions */
4179 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4180 NORM_IGNORECASE | SORT_STRINGSORT,
4181 s, 2, parmA, -1) == CSTR_EQUAL) {
4182 /* /A supplied, so evaluate expressions and set variables appropriately */
4183 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4184 /* of the final computation */
4185 int result = 0;
4186 int rc = 0;
4187 WCHAR *thisexpr;
4188 WCHAR *src,*dst;
4190 /* Remove all quotes before doing any calculations */
4191 thisexpr = heap_alloc((strlenW(s+2)+1) * sizeof(WCHAR));
4192 src = s+2;
4193 dst = thisexpr;
4194 while (*src) {
4195 if (*src != '"') *dst++ = *src;
4196 src++;
4198 *dst = 0;
4200 /* Now calculate the results of the expression */
4201 src = thisexpr;
4202 rc = WCMD_handleExpression(&src, &result, 0);
4203 heap_free(thisexpr);
4205 /* If parsing failed, issue the error message */
4206 if (rc > 0) {
4207 WCMD_output_stderr(WCMD_LoadMessage(rc));
4208 return;
4211 /* If we have no context (interactive or cmd.exe /c) print the final result */
4212 if (!context) {
4213 static const WCHAR fmt[] = {'%','d','\0'};
4214 sprintfW(string, fmt, result);
4215 WCMD_output_asis(string);
4218 } else {
4219 DWORD gle;
4221 /* set "var=value"jim ignores anything after the last quote */
4222 if (*s=='\"') {
4223 WCHAR *lastquote;
4224 lastquote = WCMD_strip_quotes(s);
4225 if (lastquote) *lastquote = 0x00;
4226 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4229 p = strchrW (s, '=');
4230 if (p == NULL) {
4231 env = GetEnvironmentStringsW();
4232 if (WCMD_setshow_sortenv( env, s ) == 0) {
4233 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4234 errorlevel = 1;
4236 return;
4238 *p++ = '\0';
4240 if (strlenW(p) == 0) p = NULL;
4241 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4242 wine_dbgstr_w(p));
4243 status = SetEnvironmentVariableW(s, p);
4244 gle = GetLastError();
4245 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4246 errorlevel = 1;
4247 } else if (!status) WCMD_print_error();
4248 else errorlevel = 0;
4252 /****************************************************************************
4253 * WCMD_setshow_path
4255 * Set/Show the path environment variable
4258 void WCMD_setshow_path (const WCHAR *args) {
4260 WCHAR string[1024];
4261 DWORD status;
4262 static const WCHAR pathW[] = {'P','A','T','H','\0'};
4263 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
4265 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
4266 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
4267 if (status != 0) {
4268 WCMD_output_asis ( pathEqW);
4269 WCMD_output_asis ( string);
4270 WCMD_output_asis ( newlineW);
4272 else {
4273 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4276 else {
4277 if (*args == '=') args++; /* Skip leading '=' */
4278 status = SetEnvironmentVariableW(pathW, args);
4279 if (!status) WCMD_print_error();
4283 /****************************************************************************
4284 * WCMD_setshow_prompt
4286 * Set or show the command prompt.
4289 void WCMD_setshow_prompt (void) {
4291 WCHAR *s;
4292 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
4294 if (strlenW(param1) == 0) {
4295 SetEnvironmentVariableW(promptW, NULL);
4297 else {
4298 s = param1;
4299 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4300 if (strlenW(s) == 0) {
4301 SetEnvironmentVariableW(promptW, NULL);
4303 else SetEnvironmentVariableW(promptW, s);
4307 /****************************************************************************
4308 * WCMD_setshow_time
4310 * Set/Show the system time
4311 * FIXME: Can't change time yet
4314 void WCMD_setshow_time (void) {
4316 WCHAR curtime[64], buffer[64];
4317 DWORD count;
4318 SYSTEMTIME st;
4319 static const WCHAR parmT[] = {'/','T','\0'};
4321 if (strlenW(param1) == 0) {
4322 GetLocalTime(&st);
4323 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
4324 curtime, sizeof(curtime)/sizeof(WCHAR))) {
4325 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4326 if (strstrW (quals, parmT) == NULL) {
4327 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4328 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4329 if (count > 2) {
4330 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4334 else WCMD_print_error ();
4336 else {
4337 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4341 /****************************************************************************
4342 * WCMD_shift
4344 * Shift batch parameters.
4345 * Optional /n says where to start shifting (n=0-8)
4348 void WCMD_shift (const WCHAR *args) {
4349 int start;
4351 if (context != NULL) {
4352 WCHAR *pos = strchrW(args, '/');
4353 int i;
4355 if (pos == NULL) {
4356 start = 0;
4357 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4358 start = (*(pos+1) - '0');
4359 } else {
4360 SetLastError(ERROR_INVALID_PARAMETER);
4361 WCMD_print_error();
4362 return;
4365 WINE_TRACE("Shifting variables, starting at %d\n", start);
4366 for (i=start;i<=8;i++) {
4367 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4369 context -> shift_count[9] = context -> shift_count[9] + 1;
4374 /****************************************************************************
4375 * WCMD_start
4377 void WCMD_start(WCHAR *args)
4379 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
4380 '\\','s','t','a','r','t','.','e','x','e',0};
4381 static const WCHAR startDelims[] = { ' ', '\t', '/', '\0' };
4382 static const WCHAR prefixQuote[] = {'"','\\','"','\0'};
4383 static const WCHAR postfixQuote[] = {'\\','"','"','\0'};
4384 int argno;
4385 int have_title;
4386 WCHAR file[MAX_PATH];
4387 WCHAR *cmdline, *cmdline_params;
4388 STARTUPINFOW st;
4389 PROCESS_INFORMATION pi;
4391 GetWindowsDirectoryW( file, MAX_PATH );
4392 strcatW( file, exeW );
4393 cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 8) * sizeof(WCHAR) );
4394 strcpyW( cmdline, file );
4395 strcatW( cmdline, spaceW );
4396 cmdline_params = cmdline + strlenW(cmdline);
4398 /* The start built-in has some special command-line parsing properties
4399 * which will be outlined here.
4401 * both '\t' and ' ' are argument separators
4402 * '/' has a special double role as both separator and switch prefix, e.g.
4404 * > start /low/i
4405 * or
4406 * > start "title"/i
4408 * are valid ways to pass multiple options to start. In the latter case
4409 * '/i' is not a part of the title but parsed as a switch.
4411 * However, '=', ';' and ',' are not separators:
4412 * > start "deus"=ex,machina
4414 * will in fact open a console titled 'deus=ex,machina'
4416 * The title argument parsing code is only interested in quotes themselves,
4417 * it does not respect escaping of any kind and all quotes are dropped
4418 * from the resulting title, therefore:
4420 * > start "\"" hello"/low
4422 * actually opens a console titled '\ hello' with low priorities.
4424 * To not break compatibility with wine programs relying on
4425 * wine's separate 'start.exe', this program's peculiar console
4426 * title parsing is actually implemented in 'cmd.exe' which is the
4427 * application native Windows programs will use to invoke 'start'.
4429 * WCMD_parameter_with_delims will take care of everything for us.
4431 have_title = FALSE;
4432 for (argno=0; ; argno++) {
4433 WCHAR *thisArg, *argN;
4435 argN = NULL;
4436 thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, startDelims);
4438 /* No more parameters */
4439 if (!argN)
4440 break;
4442 /* Found the title */
4443 if (argN[0] == '"') {
4444 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
4445 have_title = TRUE;
4447 /* Copy all of the cmdline processed */
4448 memcpy(cmdline_params, args, sizeof(WCHAR) * (argN - args));
4449 cmdline_params[argN - args] = '\0';
4451 /* Add quoted title */
4452 strcatW(cmdline_params, prefixQuote);
4453 strcatW(cmdline_params, thisArg);
4454 strcatW(cmdline_params, postfixQuote);
4456 /* Concatenate remaining command-line */
4457 thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, startDelims);
4458 strcatW(cmdline_params, argN + strlenW(thisArg));
4460 break;
4463 /* Skipping a regular argument? */
4464 else if (argN != args && argN[-1] == '/') {
4465 continue;
4467 /* Not an argument nor the title, start of program arguments,
4468 * stop looking for title.
4470 } else
4471 break;
4474 /* build command-line if not built yet */
4475 if (!have_title) {
4476 strcatW( cmdline, args );
4479 memset( &st, 0, sizeof(STARTUPINFOW) );
4480 st.cb = sizeof(STARTUPINFOW);
4482 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4484 WaitForSingleObject( pi.hProcess, INFINITE );
4485 GetExitCodeProcess( pi.hProcess, &errorlevel );
4486 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4487 CloseHandle(pi.hProcess);
4488 CloseHandle(pi.hThread);
4490 else
4492 SetLastError(ERROR_FILE_NOT_FOUND);
4493 WCMD_print_error ();
4494 errorlevel = 9009;
4496 heap_free(cmdline);
4499 /****************************************************************************
4500 * WCMD_title
4502 * Set the console title
4504 void WCMD_title (const WCHAR *args) {
4505 SetConsoleTitleW(args);
4508 /****************************************************************************
4509 * WCMD_type
4511 * Copy a file to standard output.
4514 void WCMD_type (WCHAR *args) {
4516 int argno = 0;
4517 WCHAR *argN = args;
4518 BOOL writeHeaders = FALSE;
4520 if (param1[0] == 0x00) {
4521 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4522 return;
4525 if (param2[0] != 0x00) writeHeaders = TRUE;
4527 /* Loop through all args */
4528 errorlevel = 0;
4529 while (argN) {
4530 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4532 HANDLE h;
4533 WCHAR buffer[512];
4534 DWORD count;
4536 if (!argN) break;
4538 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4539 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4540 FILE_ATTRIBUTE_NORMAL, NULL);
4541 if (h == INVALID_HANDLE_VALUE) {
4542 WCMD_print_error ();
4543 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4544 errorlevel = 1;
4545 } else {
4546 if (writeHeaders) {
4547 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
4548 WCMD_output(fmt, thisArg);
4550 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
4551 if (count == 0) break; /* ReadFile reports success on EOF! */
4552 buffer[count] = 0;
4553 WCMD_output_asis (buffer);
4555 CloseHandle (h);
4560 /****************************************************************************
4561 * WCMD_more
4563 * Output either a file or stdin to screen in pages
4566 void WCMD_more (WCHAR *args) {
4568 int argno = 0;
4569 WCHAR *argN = args;
4570 WCHAR moreStr[100];
4571 WCHAR moreStrPage[100];
4572 WCHAR buffer[512];
4573 DWORD count;
4574 static const WCHAR moreStart[] = {'-','-',' ','\0'};
4575 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
4576 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
4577 ')',' ','-','-','\n','\0'};
4578 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
4580 /* Prefix the NLS more with '-- ', then load the text */
4581 errorlevel = 0;
4582 strcpyW(moreStr, moreStart);
4583 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
4584 (sizeof(moreStr)/sizeof(WCHAR))-3);
4586 if (param1[0] == 0x00) {
4588 /* Wine implements pipes via temporary files, and hence stdin is
4589 effectively reading from the file. This means the prompts for
4590 more are satisfied by the next line from the input (file). To
4591 avoid this, ensure stdin is to the console */
4592 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4593 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
4594 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4595 FILE_ATTRIBUTE_NORMAL, 0);
4596 WINE_TRACE("No parms - working probably in pipe mode\n");
4597 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4599 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4600 once you get in this bit unless due to a pipe, it's going to end badly... */
4601 wsprintfW(moreStrPage, moreFmt, moreStr);
4603 WCMD_enter_paged_mode(moreStrPage);
4604 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4605 if (count == 0) break; /* ReadFile reports success on EOF! */
4606 buffer[count] = 0;
4607 WCMD_output_asis (buffer);
4609 WCMD_leave_paged_mode();
4611 /* Restore stdin to what it was */
4612 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4613 CloseHandle(hConIn);
4615 return;
4616 } else {
4617 BOOL needsPause = FALSE;
4619 /* Loop through all args */
4620 WINE_TRACE("Parms supplied - working through each file\n");
4621 WCMD_enter_paged_mode(moreStrPage);
4623 while (argN) {
4624 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4625 HANDLE h;
4627 if (!argN) break;
4629 if (needsPause) {
4631 /* Wait */
4632 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
4633 WCMD_leave_paged_mode();
4634 WCMD_output_asis(moreStrPage);
4635 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4636 WCMD_enter_paged_mode(moreStrPage);
4640 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4641 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4642 FILE_ATTRIBUTE_NORMAL, NULL);
4643 if (h == INVALID_HANDLE_VALUE) {
4644 WCMD_print_error ();
4645 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4646 errorlevel = 1;
4647 } else {
4648 ULONG64 curPos = 0;
4649 ULONG64 fileLen = 0;
4650 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4652 /* Get the file size */
4653 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4654 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4656 needsPause = TRUE;
4657 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4658 if (count == 0) break; /* ReadFile reports success on EOF! */
4659 buffer[count] = 0;
4660 curPos += count;
4662 /* Update % count (would be used in WCMD_output_asis as prompt) */
4663 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
4665 WCMD_output_asis (buffer);
4667 CloseHandle (h);
4671 WCMD_leave_paged_mode();
4675 /****************************************************************************
4676 * WCMD_verify
4678 * Display verify flag.
4679 * FIXME: We don't actually do anything with the verify flag other than toggle
4680 * it...
4683 void WCMD_verify (const WCHAR *args) {
4685 int count;
4687 count = strlenW(args);
4688 if (count == 0) {
4689 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
4690 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
4691 return;
4693 if (lstrcmpiW(args, onW) == 0) {
4694 verify_mode = TRUE;
4695 return;
4697 else if (lstrcmpiW(args, offW) == 0) {
4698 verify_mode = FALSE;
4699 return;
4701 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4704 /****************************************************************************
4705 * WCMD_version
4707 * Display version info.
4710 void WCMD_version (void) {
4712 WCMD_output_asis (version_string);
4716 /****************************************************************************
4717 * WCMD_volume
4719 * Display volume information (set_label = FALSE)
4720 * Additionally set volume label (set_label = TRUE)
4721 * Returns 1 on success, 0 otherwise
4724 int WCMD_volume(BOOL set_label, const WCHAR *path)
4726 DWORD count, serial;
4727 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4728 BOOL status;
4730 if (strlenW(path) == 0) {
4731 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
4732 if (!status) {
4733 WCMD_print_error ();
4734 return 0;
4736 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
4737 &serial, NULL, NULL, NULL, 0);
4739 else {
4740 static const WCHAR fmt[] = {'%','s','\\','\0'};
4741 if ((path[1] != ':') || (strlenW(path) != 2)) {
4742 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4743 return 0;
4745 wsprintfW (curdir, fmt, path);
4746 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
4747 &serial, NULL,
4748 NULL, NULL, 0);
4750 if (!status) {
4751 WCMD_print_error ();
4752 return 0;
4754 if (label[0] != '\0') {
4755 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4756 curdir[0], label);
4758 else {
4759 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4760 curdir[0]);
4762 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4763 HIWORD(serial), LOWORD(serial));
4764 if (set_label) {
4765 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4766 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4767 if (count > 1) {
4768 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4769 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4771 if (strlenW(path) != 0) {
4772 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4774 else {
4775 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4778 return 1;
4781 /**************************************************************************
4782 * WCMD_exit
4784 * Exit either the process, or just this batch program
4788 void WCMD_exit (CMD_LIST **cmdList) {
4790 static const WCHAR parmB[] = {'/','B','\0'};
4791 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
4793 if (context && lstrcmpiW(quals, parmB) == 0) {
4794 errorlevel = rc;
4795 context -> skip_rest = TRUE;
4796 *cmdList = NULL;
4797 } else {
4798 ExitProcess(rc);
4803 /*****************************************************************************
4804 * WCMD_assoc
4806 * Lists or sets file associations (assoc = TRUE)
4807 * Lists or sets file types (assoc = FALSE)
4809 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4811 HKEY key;
4812 DWORD accessOptions = KEY_READ;
4813 WCHAR *newValue;
4814 LONG rc = ERROR_SUCCESS;
4815 WCHAR keyValue[MAXSTRING];
4816 DWORD valueLen = MAXSTRING;
4817 HKEY readKey;
4818 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
4819 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4821 /* See if parameter includes '=' */
4822 errorlevel = 0;
4823 newValue = strchrW(args, '=');
4824 if (newValue) accessOptions |= KEY_WRITE;
4826 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4827 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
4828 accessOptions, &key) != ERROR_SUCCESS) {
4829 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4830 return;
4833 /* If no parameters then list all associations */
4834 if (*args == 0x00) {
4835 int index = 0;
4837 /* Enumerate all the keys */
4838 while (rc != ERROR_NO_MORE_ITEMS) {
4839 WCHAR keyName[MAXSTRING];
4840 DWORD nameLen;
4842 /* Find the next value */
4843 nameLen = MAXSTRING;
4844 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4846 if (rc == ERROR_SUCCESS) {
4848 /* Only interested in extension ones if assoc, or others
4849 if not assoc */
4850 if ((keyName[0] == '.' && assoc) ||
4851 (!(keyName[0] == '.') && (!assoc)))
4853 WCHAR subkey[MAXSTRING];
4854 strcpyW(subkey, keyName);
4855 if (!assoc) strcatW(subkey, shOpCmdW);
4857 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4859 valueLen = sizeof(keyValue)/sizeof(WCHAR);
4860 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4861 WCMD_output_asis(keyName);
4862 WCMD_output_asis(equalW);
4863 /* If no default value found, leave line empty after '=' */
4864 if (rc == ERROR_SUCCESS) {
4865 WCMD_output_asis(keyValue);
4867 WCMD_output_asis(newlineW);
4868 RegCloseKey(readKey);
4874 } else {
4876 /* Parameter supplied - if no '=' on command line, it's a query */
4877 if (newValue == NULL) {
4878 WCHAR *space;
4879 WCHAR subkey[MAXSTRING];
4881 /* Query terminates the parameter at the first space */
4882 strcpyW(keyValue, args);
4883 space = strchrW(keyValue, ' ');
4884 if (space) *space=0x00;
4886 /* Set up key name */
4887 strcpyW(subkey, keyValue);
4888 if (!assoc) strcatW(subkey, shOpCmdW);
4890 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4892 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4893 WCMD_output_asis(args);
4894 WCMD_output_asis(equalW);
4895 /* If no default value found, leave line empty after '=' */
4896 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4897 WCMD_output_asis(newlineW);
4898 RegCloseKey(readKey);
4900 } else {
4901 WCHAR msgbuffer[MAXSTRING];
4903 /* Load the translated 'File association not found' */
4904 if (assoc) {
4905 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4906 } else {
4907 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4909 WCMD_output_stderr(msgbuffer, keyValue);
4910 errorlevel = 2;
4913 /* Not a query - it's a set or clear of a value */
4914 } else {
4916 WCHAR subkey[MAXSTRING];
4918 /* Get pointer to new value */
4919 *newValue = 0x00;
4920 newValue++;
4922 /* Set up key name */
4923 strcpyW(subkey, args);
4924 if (!assoc) strcatW(subkey, shOpCmdW);
4926 /* If nothing after '=' then clear value - only valid for ASSOC */
4927 if (*newValue == 0x00) {
4929 if (assoc) rc = RegDeleteKeyW(key, args);
4930 if (assoc && rc == ERROR_SUCCESS) {
4931 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4933 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4934 WCMD_print_error();
4935 errorlevel = 2;
4937 } else {
4938 WCHAR msgbuffer[MAXSTRING];
4940 /* Load the translated 'File association not found' */
4941 if (assoc) {
4942 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
4943 sizeof(msgbuffer)/sizeof(WCHAR));
4944 } else {
4945 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
4946 sizeof(msgbuffer)/sizeof(WCHAR));
4948 WCMD_output_stderr(msgbuffer, args);
4949 errorlevel = 2;
4952 /* It really is a set value = contents */
4953 } else {
4954 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4955 accessOptions, NULL, &readKey, NULL);
4956 if (rc == ERROR_SUCCESS) {
4957 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4958 (LPBYTE)newValue,
4959 sizeof(WCHAR) * (strlenW(newValue) + 1));
4960 RegCloseKey(readKey);
4963 if (rc != ERROR_SUCCESS) {
4964 WCMD_print_error();
4965 errorlevel = 2;
4966 } else {
4967 WCMD_output_asis(args);
4968 WCMD_output_asis(equalW);
4969 WCMD_output_asis(newValue);
4970 WCMD_output_asis(newlineW);
4976 /* Clean up */
4977 RegCloseKey(key);
4980 /****************************************************************************
4981 * WCMD_color
4983 * Colors the terminal screen.
4986 void WCMD_color (void) {
4988 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4989 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4991 if (param1[0] != 0x00 && strlenW(param1) > 2) {
4992 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4993 return;
4996 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4998 COORD topLeft;
4999 DWORD screenSize;
5000 DWORD color = 0;
5002 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
5004 topLeft.X = 0;
5005 topLeft.Y = 0;
5007 /* Convert the color hex digits */
5008 if (param1[0] == 0x00) {
5009 color = defaultColor;
5010 } else {
5011 color = strtoulW(param1, NULL, 16);
5014 /* Fail if fg == bg color */
5015 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
5016 errorlevel = 1;
5017 return;
5020 /* Set the current screen contents and ensure all future writes
5021 remain this color */
5022 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
5023 SetConsoleTextAttribute(hStdOut, color);
5027 /****************************************************************************
5028 * WCMD_mklink
5031 void WCMD_mklink(WCHAR *args)
5033 int argno = 0;
5034 WCHAR *argN = args;
5035 BOOL isdir = FALSE;
5036 BOOL junction = FALSE;
5037 BOOL hard = FALSE;
5038 BOOL ret = FALSE;
5039 WCHAR file1[MAX_PATH];
5040 WCHAR file2[MAX_PATH];
5041 static const WCHAR optD[] = {'/', 'D', '\0'};
5042 static const WCHAR optH[] = {'/', 'H', '\0'};
5043 static const WCHAR optJ[] = {'/', 'J', '\0'};
5045 if (param1[0] == 0x00 || param2[0] == 0x00) {
5046 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
5047 return;
5050 file1[0] = 0;
5052 while (argN) {
5053 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
5055 if (!argN) break;
5057 WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
5059 if(lstrcmpiW(thisArg, optD) == 0)
5060 isdir = TRUE;
5061 else if(lstrcmpiW(thisArg, optH) == 0)
5062 hard = TRUE;
5063 else if(lstrcmpiW(thisArg, optJ) == 0)
5064 junction = TRUE;
5065 else {
5066 if(!file1[0])
5067 lstrcpyW(file1, thisArg);
5068 else
5069 lstrcpyW(file2, thisArg);
5073 if(hard)
5074 ret = CreateHardLinkW(file1, file2, NULL);
5075 else if(!junction)
5076 ret = CreateSymbolicLinkW(file1, file2, isdir);
5077 else
5078 WINE_TRACE("Juction links currently not supported.\n");
5080 if(!ret)
5081 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), file1);