winex11: Keep the result string in the IME UI window procedure.
[wine.git] / programs / cmd / builtins.c
blob1da1f67e60c4bc7f6f6852a5b5f6ac324cacf86f
1 /*
2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * FIXME:
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
31 #include "wcmd.h"
32 #include <shellapi.h>
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
37 extern int defaultColor;
38 extern BOOL echo_mode;
39 extern BOOL interactive;
41 struct env_stack *pushd_directories;
42 const WCHAR dotW[] = {'.','\0'};
43 const WCHAR dotdotW[] = {'.','.','\0'};
44 const WCHAR nullW[] = {'\0'};
45 const WCHAR starW[] = {'*','\0'};
46 const WCHAR slashW[] = {'\\','\0'};
47 const WCHAR equalW[] = {'=','\0'};
48 const WCHAR wildcardsW[] = {'*','?','\0'};
49 const WCHAR slashstarW[] = {'\\','*','\0'};
50 const WCHAR deviceW[] = {'\\','\\','.','\\','\0'};
51 const WCHAR inbuilt[][10] = {
52 {'C','A','L','L','\0'},
53 {'C','D','\0'},
54 {'C','H','D','I','R','\0'},
55 {'C','L','S','\0'},
56 {'C','O','P','Y','\0'},
57 {'C','T','T','Y','\0'},
58 {'D','A','T','E','\0'},
59 {'D','E','L','\0'},
60 {'D','I','R','\0'},
61 {'E','C','H','O','\0'},
62 {'E','R','A','S','E','\0'},
63 {'F','O','R','\0'},
64 {'G','O','T','O','\0'},
65 {'H','E','L','P','\0'},
66 {'I','F','\0'},
67 {'L','A','B','E','L','\0'},
68 {'M','D','\0'},
69 {'M','K','D','I','R','\0'},
70 {'M','O','V','E','\0'},
71 {'P','A','T','H','\0'},
72 {'P','A','U','S','E','\0'},
73 {'P','R','O','M','P','T','\0'},
74 {'R','E','M','\0'},
75 {'R','E','N','\0'},
76 {'R','E','N','A','M','E','\0'},
77 {'R','D','\0'},
78 {'R','M','D','I','R','\0'},
79 {'S','E','T','\0'},
80 {'S','H','I','F','T','\0'},
81 {'S','T','A','R','T','\0'},
82 {'T','I','M','E','\0'},
83 {'T','I','T','L','E','\0'},
84 {'T','Y','P','E','\0'},
85 {'V','E','R','I','F','Y','\0'},
86 {'V','E','R','\0'},
87 {'V','O','L','\0'},
88 {'E','N','D','L','O','C','A','L','\0'},
89 {'S','E','T','L','O','C','A','L','\0'},
90 {'P','U','S','H','D','\0'},
91 {'P','O','P','D','\0'},
92 {'A','S','S','O','C','\0'},
93 {'C','O','L','O','R','\0'},
94 {'F','T','Y','P','E','\0'},
95 {'M','O','R','E','\0'},
96 {'C','H','O','I','C','E','\0'},
97 {'E','X','I','T','\0'}
99 static const WCHAR externals[][10] = {
100 {'A','T','T','R','I','B','\0'},
101 {'X','C','O','P','Y','\0'}
103 static const WCHAR onW[] = {'O','N','\0'};
104 static const WCHAR offW[] = {'O','F','F','\0'};
105 static const WCHAR parmY[] = {'/','Y','\0'};
106 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
107 static const WCHAR eqeqW[] = {'=','=','\0'};
109 static HINSTANCE hinst;
110 struct env_stack *saved_environment;
111 static BOOL verify_mode = FALSE;
113 /* set /a routines work from single character operators, but some of the
114 operators are multiple character ones, especially the assignment ones.
115 Temporarily represent these using the values below on the operator stack */
116 #define OP_POSITIVE 'P'
117 #define OP_NEGATIVE 'N'
118 #define OP_ASSSIGNMUL 'a'
119 #define OP_ASSSIGNDIV 'b'
120 #define OP_ASSSIGNMOD 'c'
121 #define OP_ASSSIGNADD 'd'
122 #define OP_ASSSIGNSUB 'e'
123 #define OP_ASSSIGNAND 'f'
124 #define OP_ASSSIGNNOT 'g'
125 #define OP_ASSSIGNOR 'h'
126 #define OP_ASSSIGNSHL 'i'
127 #define OP_ASSSIGNSHR 'j'
129 /* This maintains a stack of operators, holding both the operator precedence
130 and the single character representation of the operator in question */
131 typedef struct _OPSTACK
133 int precedence;
134 WCHAR op;
135 struct _OPSTACK *next;
136 } OPSTACK;
138 /* This maintains a stack of values, where each value can either be a
139 numeric value, or a string representing an environment variable */
140 typedef struct _VARSTACK
142 BOOL isnum;
143 WCHAR *variable;
144 int value;
145 struct _VARSTACK *next;
146 } VARSTACK;
148 /* This maintains a mapping between the calculated operator and the
149 single character representation for the assignment operators. */
150 static struct
152 WCHAR op;
153 WCHAR calculatedop;
154 } calcassignments[] =
156 {'*', OP_ASSSIGNMUL},
157 {'/', OP_ASSSIGNDIV},
158 {'%', OP_ASSSIGNMOD},
159 {'+', OP_ASSSIGNADD},
160 {'-', OP_ASSSIGNSUB},
161 {'&', OP_ASSSIGNAND},
162 {'^', OP_ASSSIGNNOT},
163 {'|', OP_ASSSIGNOR},
164 {'<', OP_ASSSIGNSHL},
165 {'>', OP_ASSSIGNSHR},
166 {' ',' '}
169 /**************************************************************************
170 * WCMD_ask_confirm
172 * Issue a message and ask for confirmation, waiting on a valid answer.
174 * Returns True if Y (or A) answer is selected
175 * If optionAll contains a pointer, ALL is allowed, and if answered
176 * set to TRUE
179 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
180 BOOL *optionAll) {
182 UINT msgid;
183 WCHAR confirm[MAXSTRING];
184 WCHAR options[MAXSTRING];
185 WCHAR Ybuffer[MAXSTRING];
186 WCHAR Nbuffer[MAXSTRING];
187 WCHAR Abuffer[MAXSTRING];
188 WCHAR answer[MAX_PATH] = {'\0'};
189 DWORD count = 0;
191 /* Load the translated valid answers */
192 if (showSureText)
193 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
194 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
195 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
196 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
197 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
198 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
200 /* Loop waiting on a valid answer */
201 if (optionAll)
202 *optionAll = FALSE;
203 while (1)
205 WCMD_output_asis (message);
206 if (showSureText)
207 WCMD_output_asis (confirm);
208 WCMD_output_asis (options);
209 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
210 answer[0] = toupperW(answer[0]);
211 if (answer[0] == Ybuffer[0])
212 return TRUE;
213 if (answer[0] == Nbuffer[0])
214 return FALSE;
215 if (optionAll && answer[0] == Abuffer[0])
217 *optionAll = TRUE;
218 return TRUE;
223 /****************************************************************************
224 * WCMD_clear_screen
226 * Clear the terminal screen.
229 void WCMD_clear_screen (void) {
231 /* Emulate by filling the screen from the top left to bottom right with
232 spaces, then moving the cursor to the top left afterwards */
233 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
234 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
236 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
238 COORD topLeft;
239 DWORD screenSize, written;
241 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
243 topLeft.X = 0;
244 topLeft.Y = 0;
245 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
246 FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
247 SetConsoleCursorPosition(hStdOut, topLeft);
251 /****************************************************************************
252 * WCMD_change_tty
254 * Change the default i/o device (ie redirect STDin/STDout).
257 void WCMD_change_tty (void) {
259 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
263 /****************************************************************************
264 * WCMD_choice
268 void WCMD_choice (const WCHAR * args) {
270 static const WCHAR bellW[] = {7,0};
271 static const WCHAR commaW[] = {',',0};
272 static const WCHAR bracket_open[] = {'[',0};
273 static const WCHAR bracket_close[] = {']','?',0};
274 WCHAR answer[16];
275 WCHAR buffer[16];
276 WCHAR *ptr = NULL;
277 WCHAR *opt_c = NULL;
278 WCHAR *my_command = NULL;
279 WCHAR opt_default = 0;
280 DWORD opt_timeout = 0;
281 DWORD count;
282 DWORD oldmode;
283 BOOL have_console;
284 BOOL opt_n = FALSE;
285 BOOL opt_s = FALSE;
287 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
288 errorlevel = 0;
290 my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
292 ptr = WCMD_skip_leading_spaces(my_command);
293 while (*ptr == '/') {
294 switch (toupperW(ptr[1])) {
295 case 'C':
296 ptr += 2;
297 /* the colon is optional */
298 if (*ptr == ':')
299 ptr++;
301 if (!*ptr || isspaceW(*ptr)) {
302 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
303 heap_free(my_command);
304 return;
307 /* remember the allowed keys (overwrite previous /C option) */
308 opt_c = ptr;
309 while (*ptr && (!isspaceW(*ptr)))
310 ptr++;
312 if (*ptr) {
313 /* terminate allowed chars */
314 *ptr = 0;
315 ptr = WCMD_skip_leading_spaces(&ptr[1]);
317 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
318 break;
320 case 'N':
321 opt_n = TRUE;
322 ptr = WCMD_skip_leading_spaces(&ptr[2]);
323 break;
325 case 'S':
326 opt_s = TRUE;
327 ptr = WCMD_skip_leading_spaces(&ptr[2]);
328 break;
330 case 'T':
331 ptr = &ptr[2];
332 /* the colon is optional */
333 if (*ptr == ':')
334 ptr++;
336 opt_default = *ptr++;
338 if (!opt_default || (*ptr != ',')) {
339 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
340 heap_free(my_command);
341 return;
343 ptr++;
345 count = 0;
346 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
347 count++;
348 ptr++;
351 answer[count] = 0;
352 opt_timeout = atoiW(answer);
354 ptr = WCMD_skip_leading_spaces(ptr);
355 break;
357 default:
358 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
359 heap_free(my_command);
360 return;
364 if (opt_timeout)
365 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
367 if (have_console)
368 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
370 /* use default keys, when needed: localized versions of "Y"es and "No" */
371 if (!opt_c) {
372 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
373 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
374 opt_c = buffer;
375 buffer[2] = 0;
378 /* print the question, when needed */
379 if (*ptr)
380 WCMD_output_asis(ptr);
382 if (!opt_s) {
383 struprW(opt_c);
384 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
387 if (!opt_n) {
388 /* print a list of all allowed answers inside brackets */
389 WCMD_output_asis(bracket_open);
390 ptr = opt_c;
391 answer[1] = 0;
392 while ((answer[0] = *ptr++)) {
393 WCMD_output_asis(answer);
394 if (*ptr)
395 WCMD_output_asis(commaW);
397 WCMD_output_asis(bracket_close);
400 while (TRUE) {
402 /* FIXME: Add support for option /T */
403 answer[1] = 0; /* terminate single character string */
404 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
406 if (!opt_s)
407 answer[0] = toupperW(answer[0]);
409 ptr = strchrW(opt_c, answer[0]);
410 if (ptr) {
411 WCMD_output_asis(answer);
412 WCMD_output_asis(newlineW);
413 if (have_console)
414 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
416 errorlevel = (ptr - opt_c) + 1;
417 WINE_TRACE("answer: %d\n", errorlevel);
418 heap_free(my_command);
419 return;
421 else
423 /* key not allowed: play the bell */
424 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
425 WCMD_output_asis(bellW);
430 /****************************************************************************
431 * WCMD_AppendEOF
433 * Adds an EOF onto the end of a file
434 * Returns TRUE on success
436 static BOOL WCMD_AppendEOF(WCHAR *filename)
438 HANDLE h;
439 DWORD bytes_written;
441 char eof = '\x1a';
443 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
444 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
445 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
447 if (h == INVALID_HANDLE_VALUE) {
448 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
449 return FALSE;
450 } else {
451 SetFilePointer (h, 0, NULL, FILE_END);
452 if (!WriteFile(h, &eof, 1, &bytes_written, NULL)) {
453 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
454 CloseHandle(h);
455 return FALSE;
457 CloseHandle(h);
459 return TRUE;
462 /****************************************************************************
463 * WCMD_IsSameFile
465 * Checks if the two paths reference to the same file
467 static BOOL WCMD_IsSameFile(const WCHAR *name1, const WCHAR *name2)
469 BOOL ret = FALSE;
470 HANDLE file1 = INVALID_HANDLE_VALUE, file2 = INVALID_HANDLE_VALUE;
471 BY_HANDLE_FILE_INFORMATION info1, info2;
473 file1 = CreateFileW(name1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
474 if (file1 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file1, &info1))
475 goto end;
477 file2 = CreateFileW(name2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
478 if (file2 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file2, &info2))
479 goto end;
481 ret = info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
482 && info1.nFileIndexHigh == info2.nFileIndexHigh
483 && info1.nFileIndexLow == info2.nFileIndexLow;
484 end:
485 if (file1 != INVALID_HANDLE_VALUE)
486 CloseHandle(file1);
487 if (file2 != INVALID_HANDLE_VALUE)
488 CloseHandle(file2);
489 return ret;
492 /****************************************************************************
493 * WCMD_ManualCopy
495 * Copies from a file
496 * optionally reading only until EOF (ascii copy)
497 * optionally appending onto an existing file (append)
498 * Returns TRUE on success
500 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
502 HANDLE in,out;
503 BOOL ok;
504 DWORD bytesread, byteswritten;
506 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
507 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
509 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
510 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
511 if (in == INVALID_HANDLE_VALUE) {
512 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
513 return FALSE;
516 /* Open the output file, overwriting if not appending */
517 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
518 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
519 if (out == INVALID_HANDLE_VALUE) {
520 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
521 CloseHandle(in);
522 return FALSE;
525 /* Move to end of destination if we are going to append to it */
526 if (append) {
527 SetFilePointer(out, 0, NULL, FILE_END);
530 /* Loop copying data from source to destination until EOF read */
533 char buffer[MAXSTRING];
535 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
536 if (ok) {
538 /* Stop at first EOF */
539 if (ascii) {
540 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
541 if (ptr) bytesread = (ptr - buffer);
544 if (bytesread) {
545 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
546 if (!ok || byteswritten != bytesread) {
547 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
548 wine_dbgstr_w(dstname), GetLastError());
551 } else {
552 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
553 wine_dbgstr_w(srcname), GetLastError());
555 } while (ok && bytesread > 0);
557 CloseHandle(out);
558 CloseHandle(in);
559 return ok;
562 /****************************************************************************
563 * WCMD_copy
565 * Copy a file or wildcarded set.
566 * For ascii/binary type copies, it gets complex:
567 * Syntax on command line is
568 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
569 * Where first /a or /b sets 'mode in operation' until another is found
570 * once another is found, it applies to the file preceding the /a or /b
571 * In addition each filename can contain wildcards
572 * To make matters worse, the + may be in the same parameter (i.e. no
573 * whitespace) or with whitespace separating it
575 * ASCII mode on read == read and stop at first EOF
576 * ASCII mode on write == append EOF to destination
577 * Binary == copy as-is
579 * Design of this is to build up a list of files which will be copied into a
580 * list, then work through the list file by file.
581 * If no destination is specified, it defaults to the name of the first file in
582 * the list, but the current directory.
586 void WCMD_copy(WCHAR * args) {
588 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
589 WCHAR *thisparam;
590 int argno = 0;
591 WCHAR *rawarg;
592 WIN32_FIND_DATAW fd;
593 HANDLE hff = INVALID_HANDLE_VALUE;
594 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
595 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
596 BOOL anyconcats = FALSE; /* Have we found any + options */
597 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
598 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
599 BOOL prompt; /* Prompt before overwriting */
600 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
601 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
602 BOOL status;
603 WCHAR copycmd[4];
604 DWORD len;
605 BOOL dstisdevice = FALSE;
606 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
608 typedef struct _COPY_FILES
610 struct _COPY_FILES *next;
611 BOOL concatenate;
612 WCHAR *name;
613 int binarycopy;
614 } COPY_FILES;
615 COPY_FILES *sourcelist = NULL;
616 COPY_FILES *lastcopyentry = NULL;
617 COPY_FILES *destination = NULL;
618 COPY_FILES *thiscopy = NULL;
619 COPY_FILES *prevcopy = NULL;
621 /* Assume we were successful! */
622 errorlevel = 0;
624 /* If no args supplied at all, report an error */
625 if (param1[0] == 0x00) {
626 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
627 errorlevel = 1;
628 return;
631 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
633 /* Walk through all args, building up a list of files to process */
634 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
635 while (*(thisparam)) {
636 WCHAR *pos1, *pos2;
637 BOOL inquotes;
639 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
641 /* Handle switches */
642 if (*thisparam == '/') {
643 while (*thisparam == '/') {
644 thisparam++;
645 if (toupperW(*thisparam) == 'D') {
646 opt_d = TRUE;
647 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
648 } else if (toupperW(*thisparam) == 'Y') {
649 opt_y = TRUE;
650 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
651 opt_noty = TRUE;
652 } else if (toupperW(*thisparam) == 'V') {
653 opt_v = TRUE;
654 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
655 } else if (toupperW(*thisparam) == 'N') {
656 opt_n = TRUE;
657 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
658 } else if (toupperW(*thisparam) == 'Z') {
659 opt_z = TRUE;
660 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
661 } else if (toupperW(*thisparam) == 'A') {
662 if (binarymode != 0) {
663 binarymode = 0;
664 WINE_TRACE("Subsequent files will be handled as ASCII\n");
665 if (destination != NULL) {
666 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
667 destination->binarycopy = binarymode;
668 } else if (lastcopyentry != NULL) {
669 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
670 lastcopyentry->binarycopy = binarymode;
673 } else if (toupperW(*thisparam) == 'B') {
674 if (binarymode != 1) {
675 binarymode = 1;
676 WINE_TRACE("Subsequent files will be handled as binary\n");
677 if (destination != NULL) {
678 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
679 destination->binarycopy = binarymode;
680 } else if (lastcopyentry != NULL) {
681 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
682 lastcopyentry->binarycopy = binarymode;
685 } else {
686 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
688 thisparam++;
691 /* This parameter was purely switches, get the next one */
692 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
693 continue;
696 /* We have found something which is not a switch. If could be anything of the form
697 sourcefilename (which could be destination too)
698 + (when filename + filename syntex used)
699 sourcefilename+sourcefilename
700 +sourcefilename
701 +/b[tests show windows then ignores to end of parameter]
704 if (*thisparam=='+') {
705 if (lastcopyentry == NULL) {
706 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
707 errorlevel = 1;
708 goto exitreturn;
709 } else {
710 concatnextfilename = TRUE;
711 anyconcats = TRUE;
714 /* Move to next thing to process */
715 thisparam++;
716 if (*thisparam == 0x00)
717 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
718 continue;
721 /* We have found something to process - build a COPY_FILE block to store it */
722 thiscopy = heap_alloc(sizeof(COPY_FILES));
724 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
725 thiscopy->concatenate = concatnextfilename;
726 thiscopy->binarycopy = binarymode;
727 thiscopy->next = NULL;
729 /* Time to work out the name. Allocate at least enough space (deliberately too much to
730 leave space to append \* to the end) , then copy in character by character. Strip off
731 quotes if we find them. */
732 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
733 thiscopy->name = heap_alloc(len*sizeof(WCHAR));
734 memset(thiscopy->name, 0x00, len);
736 pos1 = thisparam;
737 pos2 = thiscopy->name;
738 inquotes = FALSE;
739 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
740 if (*pos1 == '"') {
741 inquotes = !inquotes;
742 pos1++;
743 } else *pos2++ = *pos1++;
745 *pos2 = 0;
746 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
748 /* This is either the first source, concatenated subsequent source or destination */
749 if (sourcelist == NULL) {
750 WINE_TRACE("Adding as first source part\n");
751 sourcelist = thiscopy;
752 lastcopyentry = thiscopy;
753 } else if (concatnextfilename) {
754 WINE_TRACE("Adding to source file list to be concatenated\n");
755 lastcopyentry->next = thiscopy;
756 lastcopyentry = thiscopy;
757 } else if (destination == NULL) {
758 destination = thiscopy;
759 } else {
760 /* We have processed sources and destinations and still found more to do - invalid */
761 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
762 errorlevel = 1;
763 goto exitreturn;
765 concatnextfilename = FALSE;
767 /* We either need to process the rest of the parameter or move to the next */
768 if (*pos1 == '/' || *pos1 == '+') {
769 thisparam = pos1;
770 continue;
771 } else {
772 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
776 /* Ensure we have at least one source file */
777 if (!sourcelist) {
778 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
779 errorlevel = 1;
780 goto exitreturn;
783 /* Default whether automatic overwriting is on. If we are interactive then
784 we prompt by default, otherwise we overwrite by default
785 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
786 if (opt_noty) prompt = TRUE;
787 else if (opt_y) prompt = FALSE;
788 else {
789 /* By default, we will force the overwrite in batch mode and ask for
790 * confirmation in interactive mode. */
791 prompt = interactive;
792 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
793 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
794 * default behavior. */
795 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
796 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
797 if (!lstrcmpiW (copycmd, parmY))
798 prompt = FALSE;
799 else if (!lstrcmpiW (copycmd, parmNoY))
800 prompt = TRUE;
804 /* Calculate the destination now - if none supplied, it's current dir +
805 filename of first file in list*/
806 if (destination == NULL) {
808 WINE_TRACE("No destination supplied, so need to calculate it\n");
809 strcpyW(destname, dotW);
810 strcatW(destname, slashW);
812 destination = heap_alloc(sizeof(COPY_FILES));
813 if (destination == NULL) goto exitreturn;
814 destination->concatenate = FALSE; /* Not used for destination */
815 destination->binarycopy = binarymode;
816 destination->next = NULL; /* Not used for destination */
817 destination->name = NULL; /* To be filled in */
818 destisdirectory = TRUE;
820 } else {
821 WCHAR *filenamepart;
822 DWORD attributes;
824 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
826 /* Convert to fully qualified path/filename */
827 GetFullPathNameW(destination->name, sizeof(destname)/sizeof(WCHAR), destname, &filenamepart);
828 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
830 /* If parameter is a directory, ensure it ends in \ */
831 attributes = GetFileAttributesW(destname);
832 if (ends_with_backslash( destname ) ||
833 ((attributes != INVALID_FILE_ATTRIBUTES) &&
834 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
836 destisdirectory = TRUE;
837 if (!ends_with_backslash( destname )) strcatW(destname, slashW);
838 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
842 /* Normally, the destination is the current directory unless we are
843 concatenating, in which case it's current directory plus first filename.
844 Note that if the
845 In addition by default it is a binary copy unless concatenating, when
846 the copy defaults to an ascii copy (stop at EOF). We do not know the
847 first source part yet (until we search) so flag as needing filling in. */
849 if (anyconcats) {
850 /* We have found an a+b type syntax, so destination has to be a filename
851 and we need to default to ascii copying. If we have been supplied a
852 directory as the destination, we need to defer calculating the name */
853 if (destisdirectory) appendfirstsource = TRUE;
854 if (destination->binarycopy == -1) destination->binarycopy = 0;
856 } else if (!destisdirectory) {
857 /* We have been asked to copy to a filename. Default to ascii IF the
858 source contains wildcards (true even if only one match) */
859 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
860 anyconcats = TRUE; /* We really are concatenating to a single file */
861 if (destination->binarycopy == -1) {
862 destination->binarycopy = 0;
864 } else {
865 if (destination->binarycopy == -1) {
866 destination->binarycopy = 1;
871 /* Save away the destination name*/
872 heap_free(destination->name);
873 destination->name = heap_strdupW(destname);
874 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
875 wine_dbgstr_w(destname), appendfirstsource);
877 /* Remember if the destination is a device */
878 if (strncmpW(destination->name, deviceW, strlenW(deviceW)) == 0) {
879 WINE_TRACE("Destination is a device\n");
880 dstisdevice = TRUE;
883 /* Now we need to walk the set of sources, and process each name we come to.
884 If anyconcats is true, we are writing to one file, otherwise we are using
885 the source name each time.
886 If destination exists, prompt for overwrite the first time (if concatenating
887 we ask each time until yes is answered)
888 The first source file we come across must exist (when wildcards expanded)
889 and if concatenating with overwrite prompts, each source file must exist
890 until a yes is answered. */
892 thiscopy = sourcelist;
893 prevcopy = NULL;
895 while (thiscopy != NULL) {
897 WCHAR srcpath[MAX_PATH];
898 const WCHAR *srcname;
899 WCHAR *filenamepart;
900 DWORD attributes;
901 BOOL srcisdevice = FALSE;
903 /* If it was not explicit, we now know whether we are concatenating or not and
904 hence whether to copy as binary or ascii */
905 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
907 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
908 to where the filename portion begins (used for wildcard expansion). */
909 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
910 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
912 /* If parameter is a directory, ensure it ends in \* */
913 attributes = GetFileAttributesW(srcpath);
914 if (ends_with_backslash( srcpath )) {
916 /* We need to know where the filename part starts, so append * and
917 recalculate the full resulting path */
918 strcatW(thiscopy->name, starW);
919 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
920 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
922 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
923 (attributes != INVALID_FILE_ATTRIBUTES) &&
924 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
926 /* We need to know where the filename part starts, so append \* and
927 recalculate the full resulting path */
928 strcatW(thiscopy->name, slashstarW);
929 GetFullPathNameW(thiscopy->name, sizeof(srcpath)/sizeof(WCHAR), srcpath, &filenamepart);
930 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
933 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
934 wine_dbgstr_w(srcpath), anyconcats);
936 /* If the source is a device, just use it, otherwise search */
937 if (strncmpW(srcpath, deviceW, strlenW(deviceW)) == 0) {
938 WINE_TRACE("Source is a device\n");
939 srcisdevice = TRUE;
940 srcname = &srcpath[4]; /* After the \\.\ prefix */
941 } else {
943 /* Loop through all source files */
944 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
945 hff = FindFirstFileW(srcpath, &fd);
946 if (hff != INVALID_HANDLE_VALUE) {
947 srcname = fd.cFileName;
951 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
952 do {
953 WCHAR outname[MAX_PATH];
954 BOOL overwrite;
955 BOOL appendtofirstfile = FALSE;
957 /* Skip . and .., and directories */
958 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
959 WINE_TRACE("Skipping directories\n");
960 } else {
962 /* Build final destination name */
963 strcpyW(outname, destination->name);
964 if (destisdirectory || appendfirstsource) strcatW(outname, srcname);
966 /* Build source name */
967 if (!srcisdevice) strcpyW(filenamepart, srcname);
969 /* Do we just overwrite (we do if we are writing to a device) */
970 overwrite = !prompt;
971 if (dstisdevice || (anyconcats && writtenoneconcat)) {
972 overwrite = TRUE;
975 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
976 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
977 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
978 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
980 if (!writtenoneconcat) {
981 appendtofirstfile = anyconcats && WCMD_IsSameFile(srcpath, outname);
984 /* Prompt before overwriting */
985 if (appendtofirstfile) {
986 overwrite = TRUE;
987 } else if (!overwrite) {
988 DWORD attributes = GetFileAttributesW(outname);
989 if (attributes != INVALID_FILE_ATTRIBUTES) {
990 WCHAR* question;
991 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
992 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
993 LocalFree(question);
995 else overwrite = TRUE;
998 /* If we needed to save away the first filename, do it */
999 if (appendfirstsource && overwrite) {
1000 heap_free(destination->name);
1001 destination->name = heap_strdupW(outname);
1002 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
1003 appendfirstsource = FALSE;
1004 destisdirectory = FALSE;
1007 /* Do the copy as appropriate */
1008 if (overwrite) {
1009 if (anyconcats && WCMD_IsSameFile(srcpath, outname)) {
1010 /* Silently skip if the destination file is also a source file */
1011 status = TRUE;
1012 } else if (anyconcats && writtenoneconcat) {
1013 if (thiscopy->binarycopy) {
1014 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
1015 } else {
1016 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
1018 } else if (!thiscopy->binarycopy) {
1019 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
1020 } else if (srcisdevice) {
1021 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
1022 } else {
1023 status = CopyFileW(srcpath, outname, FALSE);
1025 if (!status) {
1026 WCMD_print_error ();
1027 errorlevel = 1;
1028 } else {
1029 WINE_TRACE("Copied successfully\n");
1030 if (anyconcats) writtenoneconcat = TRUE;
1032 /* Append EOF if ascii destination and we are not going to add more onto the end
1033 Note: Testing shows windows has an optimization whereas if you have a binary
1034 copy of a file to a single destination (ie concatenation) then it does not add
1035 the EOF, hence the check on the source copy type below. */
1036 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
1037 if (!WCMD_AppendEOF(outname)) {
1038 WCMD_print_error ();
1039 errorlevel = 1;
1045 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1046 if (!srcisdevice) FindClose (hff);
1047 } else {
1048 /* Error if the first file was not found */
1049 if (!anyconcats || !writtenoneconcat) {
1050 WCMD_print_error ();
1051 errorlevel = 1;
1055 /* Step on to the next supplied source */
1056 thiscopy = thiscopy -> next;
1059 /* Append EOF if ascii destination and we were concatenating */
1060 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
1061 if (!WCMD_AppendEOF(destination->name)) {
1062 WCMD_print_error ();
1063 errorlevel = 1;
1067 /* Exit out of the routine, freeing any remaining allocated memory */
1068 exitreturn:
1070 thiscopy = sourcelist;
1071 while (thiscopy != NULL) {
1072 prevcopy = thiscopy;
1073 /* Free up this block*/
1074 thiscopy = thiscopy -> next;
1075 heap_free(prevcopy->name);
1076 heap_free(prevcopy);
1079 /* Free up the destination memory */
1080 if (destination) {
1081 heap_free(destination->name);
1082 heap_free(destination);
1085 return;
1088 /****************************************************************************
1089 * WCMD_create_dir
1091 * Create a directory (and, if needed, any intermediate directories).
1093 * Modifies its argument by replacing slashes temporarily with nulls.
1096 static BOOL create_full_path(WCHAR* path)
1098 WCHAR *p, *start;
1100 /* don't mess with drive letter portion of path, if any */
1101 start = path;
1102 if (path[1] == ':')
1103 start = path+2;
1105 /* Strip trailing slashes. */
1106 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
1107 *p = 0;
1109 /* Step through path, creating intermediate directories as needed. */
1110 /* First component includes drive letter, if any. */
1111 p = start;
1112 for (;;) {
1113 DWORD rv;
1114 /* Skip to end of component */
1115 while (*p == '\\') p++;
1116 while (*p && *p != '\\') p++;
1117 if (!*p) {
1118 /* path is now the original full path */
1119 return CreateDirectoryW(path, NULL);
1121 /* Truncate path, create intermediate directory, and restore path */
1122 *p = 0;
1123 rv = CreateDirectoryW(path, NULL);
1124 *p = '\\';
1125 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1126 return FALSE;
1128 /* notreached */
1129 return FALSE;
1132 void WCMD_create_dir (WCHAR *args) {
1133 int argno = 0;
1134 WCHAR *argN = args;
1136 if (param1[0] == 0x00) {
1137 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1138 return;
1140 /* Loop through all args */
1141 while (TRUE) {
1142 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1143 if (!argN) break;
1144 if (!create_full_path(thisArg)) {
1145 WCMD_print_error ();
1146 errorlevel = 1;
1151 /* Parse the /A options given by the user on the commandline
1152 * into a bitmask of wanted attributes (*wantSet),
1153 * and a bitmask of unwanted attributes (*wantClear).
1155 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1156 static const WCHAR parmA[] = {'/','A','\0'};
1157 WCHAR *p;
1159 /* both are strictly 'out' parameters */
1160 *wantSet=0;
1161 *wantClear=0;
1163 /* For each /A argument */
1164 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1165 /* Skip /A itself */
1166 p += 2;
1168 /* Skip optional : */
1169 if (*p == ':') p++;
1171 /* For each of the attribute specifier chars to this /A option */
1172 for (; *p != 0 && *p != '/'; p++) {
1173 BOOL negate = FALSE;
1174 DWORD mask = 0;
1176 if (*p == '-') {
1177 negate=TRUE;
1178 p++;
1181 /* Convert the attribute specifier to a bit in one of the masks */
1182 switch (*p) {
1183 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1184 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1185 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1186 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1187 default:
1188 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1190 if (negate)
1191 *wantClear |= mask;
1192 else
1193 *wantSet |= mask;
1198 /* If filename part of parameter is * or *.*,
1199 * and neither /Q nor /P options were given,
1200 * prompt the user whether to proceed.
1201 * Returns FALSE if user says no, TRUE otherwise.
1202 * *pPrompted is set to TRUE if the user is prompted.
1203 * (If /P supplied, del will prompt for individual files later.)
1205 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1206 static const WCHAR parmP[] = {'/','P','\0'};
1207 static const WCHAR parmQ[] = {'/','Q','\0'};
1209 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1210 static const WCHAR anyExt[]= {'.','*','\0'};
1211 WCHAR drive[10];
1212 WCHAR dir[MAX_PATH];
1213 WCHAR fname[MAX_PATH];
1214 WCHAR ext[MAX_PATH];
1215 WCHAR fpath[MAX_PATH];
1217 /* Convert path into actual directory spec */
1218 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1219 WCMD_splitpath(fpath, drive, dir, fname, ext);
1221 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1222 if ((strcmpW(fname, starW) == 0) &&
1223 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1225 WCHAR question[MAXSTRING];
1226 static const WCHAR fmt[] = {'%','s',' ','\0'};
1228 /* Caller uses this to suppress "file not found" warning later */
1229 *pPrompted = TRUE;
1231 /* Ask for confirmation */
1232 wsprintfW(question, fmt, fpath);
1233 return WCMD_ask_confirm(question, TRUE, NULL);
1236 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1237 return TRUE;
1240 /* Helper function for WCMD_delete().
1241 * Deletes a single file, directory, or wildcard.
1242 * If /S was given, does it recursively.
1243 * Returns TRUE if a file was deleted.
1245 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1247 static const WCHAR parmP[] = {'/','P','\0'};
1248 static const WCHAR parmS[] = {'/','S','\0'};
1249 static const WCHAR parmF[] = {'/','F','\0'};
1250 DWORD wanted_attrs;
1251 DWORD unwanted_attrs;
1252 BOOL found = FALSE;
1253 WCHAR argCopy[MAX_PATH];
1254 WIN32_FIND_DATAW fd;
1255 HANDLE hff;
1256 WCHAR fpath[MAX_PATH];
1257 WCHAR *p;
1258 BOOL handleParm = TRUE;
1260 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1262 strcpyW(argCopy, thisArg);
1263 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1264 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1266 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1267 /* Skip this arg if user declines to delete *.* */
1268 return FALSE;
1271 /* First, try to delete in the current directory */
1272 hff = FindFirstFileW(argCopy, &fd);
1273 if (hff == INVALID_HANDLE_VALUE) {
1274 handleParm = FALSE;
1275 } else {
1276 found = TRUE;
1279 /* Support del <dirname> by just deleting all files dirname\* */
1280 if (handleParm
1281 && (strchrW(argCopy,'*') == NULL)
1282 && (strchrW(argCopy,'?') == NULL)
1283 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1285 WCHAR modifiedParm[MAX_PATH];
1286 static const WCHAR slashStar[] = {'\\','*','\0'};
1288 strcpyW(modifiedParm, argCopy);
1289 strcatW(modifiedParm, slashStar);
1290 FindClose(hff);
1291 found = TRUE;
1292 WCMD_delete_one(modifiedParm);
1294 } else if (handleParm) {
1296 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1297 strcpyW (fpath, argCopy);
1298 do {
1299 p = strrchrW (fpath, '\\');
1300 if (p != NULL) {
1301 *++p = '\0';
1302 strcatW (fpath, fd.cFileName);
1304 else strcpyW (fpath, fd.cFileName);
1305 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1306 BOOL ok;
1308 /* Handle attribute matching (/A) */
1309 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1310 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1312 /* /P means prompt for each file */
1313 if (ok && strstrW (quals, parmP) != NULL) {
1314 WCHAR* question;
1316 /* Ask for confirmation */
1317 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1318 ok = WCMD_ask_confirm(question, FALSE, NULL);
1319 LocalFree(question);
1322 /* Only proceed if ok to */
1323 if (ok) {
1325 /* If file is read only, and /A:r or /F supplied, delete it */
1326 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1327 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1328 strstrW (quals, parmF) != NULL)) {
1329 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1332 /* Now do the delete */
1333 if (!DeleteFileW(fpath)) WCMD_print_error ();
1337 } while (FindNextFileW(hff, &fd) != 0);
1338 FindClose (hff);
1341 /* Now recurse into all subdirectories handling the parameter in the same way */
1342 if (strstrW (quals, parmS) != NULL) {
1344 WCHAR thisDir[MAX_PATH];
1345 int cPos;
1347 WCHAR drive[10];
1348 WCHAR dir[MAX_PATH];
1349 WCHAR fname[MAX_PATH];
1350 WCHAR ext[MAX_PATH];
1352 /* Convert path into actual directory spec */
1353 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
1354 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1356 strcpyW(thisDir, drive);
1357 strcatW(thisDir, dir);
1358 cPos = strlenW(thisDir);
1360 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1362 /* Append '*' to the directory */
1363 thisDir[cPos] = '*';
1364 thisDir[cPos+1] = 0x00;
1366 hff = FindFirstFileW(thisDir, &fd);
1368 /* Remove residual '*' */
1369 thisDir[cPos] = 0x00;
1371 if (hff != INVALID_HANDLE_VALUE) {
1372 DIRECTORY_STACK *allDirs = NULL;
1373 DIRECTORY_STACK *lastEntry = NULL;
1375 do {
1376 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1377 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1378 (strcmpW(fd.cFileName, dotW) != 0)) {
1380 DIRECTORY_STACK *nextDir;
1381 WCHAR subParm[MAX_PATH];
1383 /* Work out search parameter in sub dir */
1384 strcpyW (subParm, thisDir);
1385 strcatW (subParm, fd.cFileName);
1386 strcatW (subParm, slashW);
1387 strcatW (subParm, fname);
1388 strcatW (subParm, ext);
1389 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1391 /* Allocate memory, add to list */
1392 nextDir = heap_alloc(sizeof(DIRECTORY_STACK));
1393 if (allDirs == NULL) allDirs = nextDir;
1394 if (lastEntry != NULL) lastEntry->next = nextDir;
1395 lastEntry = nextDir;
1396 nextDir->next = NULL;
1397 nextDir->dirName = heap_strdupW(subParm);
1399 } while (FindNextFileW(hff, &fd) != 0);
1400 FindClose (hff);
1402 /* Go through each subdir doing the delete */
1403 while (allDirs != NULL) {
1404 DIRECTORY_STACK *tempDir;
1406 tempDir = allDirs->next;
1407 found |= WCMD_delete_one (allDirs->dirName);
1409 heap_free(allDirs->dirName);
1410 heap_free(allDirs);
1411 allDirs = tempDir;
1416 return found;
1419 /****************************************************************************
1420 * WCMD_delete
1422 * Delete a file or wildcarded set.
1424 * Note on /A:
1425 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1426 * - Each set is a pattern, eg /ahr /as-r means
1427 * readonly+hidden OR nonreadonly system files
1428 * - The '-' applies to a single field, ie /a:-hr means read only
1429 * non-hidden files
1432 BOOL WCMD_delete (WCHAR *args) {
1433 int argno;
1434 WCHAR *argN;
1435 BOOL argsProcessed = FALSE;
1436 BOOL foundAny = FALSE;
1438 errorlevel = 0;
1440 for (argno=0; ; argno++) {
1441 BOOL found;
1442 WCHAR *thisArg;
1444 argN = NULL;
1445 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1446 if (!argN)
1447 break; /* no more parameters */
1448 if (argN[0] == '/')
1449 continue; /* skip options */
1451 argsProcessed = TRUE;
1452 found = WCMD_delete_one(thisArg);
1453 if (!found)
1454 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1455 foundAny |= found;
1458 /* Handle no valid args */
1459 if (!argsProcessed)
1460 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1462 return foundAny;
1466 * WCMD_strtrim
1468 * Returns a trimmed version of s with all leading and trailing whitespace removed
1469 * Pre: s non NULL
1472 static WCHAR *WCMD_strtrim(const WCHAR *s)
1474 DWORD len = strlenW(s);
1475 const WCHAR *start = s;
1476 WCHAR* result;
1478 result = heap_alloc((len + 1) * sizeof(WCHAR));
1480 while (isspaceW(*start)) start++;
1481 if (*start) {
1482 const WCHAR *end = s + len - 1;
1483 while (end > start && isspaceW(*end)) end--;
1484 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1485 result[end - start + 1] = '\0';
1486 } else {
1487 result[0] = '\0';
1490 return result;
1493 /****************************************************************************
1494 * WCMD_echo
1496 * Echo input to the screen (or not). We don't try to emulate the bugs
1497 * in DOS (try typing "ECHO ON AGAIN" for an example).
1500 void WCMD_echo (const WCHAR *args)
1502 int count;
1503 const WCHAR *origcommand = args;
1504 WCHAR *trimmed;
1506 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1507 || args[0]==':' || args[0]==';' || args[0]=='/')
1508 args++;
1510 trimmed = WCMD_strtrim(args);
1511 if (!trimmed) return;
1513 count = strlenW(trimmed);
1514 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1515 && origcommand[0]!=';' && origcommand[0]!='/') {
1516 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1517 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1518 heap_free(trimmed);
1519 return;
1522 if (lstrcmpiW(trimmed, onW) == 0)
1523 echo_mode = TRUE;
1524 else if (lstrcmpiW(trimmed, offW) == 0)
1525 echo_mode = FALSE;
1526 else {
1527 WCMD_output_asis (args);
1528 WCMD_output_asis (newlineW);
1530 heap_free(trimmed);
1533 /*****************************************************************************
1534 * WCMD_part_execute
1536 * Execute a command, and any && or bracketed follow on to the command. The
1537 * first command to be executed may not be at the front of the
1538 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1540 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1541 BOOL isIF, BOOL executecmds)
1543 CMD_LIST *curPosition = *cmdList;
1544 int myDepth = (*cmdList)->bracketDepth;
1546 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d)\n", cmdList, wine_dbgstr_w(firstcmd),
1547 executecmds);
1549 /* Skip leading whitespace between condition and the command */
1550 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1552 /* Process the first command, if there is one */
1553 if (executecmds && firstcmd && *firstcmd) {
1554 WCHAR *command = heap_strdupW(firstcmd);
1555 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1556 heap_free(command);
1560 /* If it didn't move the position, step to next command */
1561 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1563 /* Process any other parts of the command */
1564 if (*cmdList) {
1565 BOOL processThese = executecmds;
1567 while (*cmdList) {
1568 static const WCHAR ifElse[] = {'e','l','s','e'};
1570 /* execute all appropriate commands */
1571 curPosition = *cmdList;
1573 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1574 *cmdList,
1575 (*cmdList)->prevDelim,
1576 (*cmdList)->bracketDepth, myDepth);
1578 /* Execute any statements appended to the line */
1579 /* FIXME: Only if previous call worked for && or failed for || */
1580 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1581 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1582 if (processThese && (*cmdList)->command) {
1583 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1584 cmdList, FALSE);
1586 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1588 /* Execute any appended to the statement with (...) */
1589 } else if ((*cmdList)->bracketDepth > myDepth) {
1590 if (processThese) {
1591 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1592 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1594 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1596 /* End of the command - does 'ELSE ' follow as the next command? */
1597 } else {
1598 if (isIF
1599 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1600 (*cmdList)->command)) {
1602 /* Swap between if and else processing */
1603 processThese = !executecmds;
1605 /* Process the ELSE part */
1606 if (processThese) {
1607 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1608 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1610 /* Skip leading whitespace between condition and the command */
1611 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1612 if (*cmd) {
1613 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1616 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1617 } else if (!processThese) {
1618 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1619 WINE_TRACE("Ignore the next command as well (next = %p)\n", *cmdList);
1620 } else {
1621 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1622 break;
1627 return;
1630 /*****************************************************************************
1631 * WCMD_parse_forf_options
1633 * Parses the for /f 'options', extracting the values and validating the
1634 * keywords. Note all keywords are optional.
1635 * Parameters:
1636 * options [I] The unparsed parameter string
1637 * eol [O] Set to the comment character (eol=x)
1638 * skip [O] Set to the number of lines to skip (skip=xx)
1639 * delims [O] Set to the token delimiters (delims=)
1640 * tokens [O] Set to the requested tokens, as provided (tokens=)
1641 * usebackq [O] Set to TRUE if usebackq found
1643 * Returns TRUE on success, FALSE on syntax error
1646 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1647 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1650 WCHAR *pos = options;
1651 int len = strlenW(pos);
1652 static const WCHAR eolW[] = {'e','o','l','='};
1653 static const WCHAR skipW[] = {'s','k','i','p','='};
1654 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1655 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1656 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1657 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1658 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1660 /* Initialize to defaults */
1661 strcpyW(delims, forf_defaultdelims);
1662 strcpyW(tokens, forf_defaulttokens);
1663 *eol = 0;
1664 *skip = 0;
1665 *usebackq = FALSE;
1667 /* Strip (optional) leading and trailing quotes */
1668 if ((*pos == '"') && (pos[len-1] == '"')) {
1669 pos[len-1] = 0;
1670 pos++;
1673 /* Process each keyword */
1674 while (pos && *pos) {
1675 if (*pos == ' ' || *pos == '\t') {
1676 pos++;
1678 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1679 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1680 pos, sizeof(eolW)/sizeof(WCHAR),
1681 eolW, sizeof(eolW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1682 *eol = *(pos + sizeof(eolW)/sizeof(WCHAR));
1683 pos = pos + sizeof(eolW)/sizeof(WCHAR) + 1;
1684 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1686 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1687 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1688 pos, sizeof(skipW)/sizeof(WCHAR),
1689 skipW, sizeof(skipW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1690 WCHAR *nextchar = NULL;
1691 pos = pos + sizeof(skipW)/sizeof(WCHAR);
1692 *skip = strtoulW(pos, &nextchar, 0);
1693 WINE_TRACE("Found skip as %d lines\n", *skip);
1694 pos = nextchar;
1696 /* Save if usebackq semantics are in effect */
1697 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1698 pos, sizeof(usebackqW)/sizeof(WCHAR),
1699 usebackqW, sizeof(usebackqW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1700 *usebackq = TRUE;
1701 pos = pos + sizeof(usebackqW)/sizeof(WCHAR);
1702 WINE_TRACE("Found usebackq\n");
1704 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1705 if you finish the optionsroot string with delims= otherwise the space is
1706 just a token delimiter! */
1707 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1708 pos, sizeof(delimsW)/sizeof(WCHAR),
1709 delimsW, sizeof(delimsW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1710 int i=0;
1712 pos = pos + sizeof(delimsW)/sizeof(WCHAR);
1713 while (*pos && *pos != ' ') {
1714 delims[i++] = *pos;
1715 pos++;
1717 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1718 delims[i++] = 0; /* Null terminate the delims */
1719 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1721 /* Save the tokens being requested */
1722 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1723 pos, sizeof(tokensW)/sizeof(WCHAR),
1724 tokensW, sizeof(tokensW)/sizeof(WCHAR)) == CSTR_EQUAL) {
1725 int i=0;
1727 pos = pos + sizeof(tokensW)/sizeof(WCHAR);
1728 while (*pos && *pos != ' ') {
1729 tokens[i++] = *pos;
1730 pos++;
1732 tokens[i++] = 0; /* Null terminate the tokens */
1733 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1735 } else {
1736 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1737 return FALSE;
1740 return TRUE;
1743 /*****************************************************************************
1744 * WCMD_add_dirstowalk
1746 * When recursing through directories (for /r), we need to add to the list of
1747 * directories still to walk, any subdirectories of the one we are processing.
1749 * Parameters
1750 * options [I] The remaining list of directories still to process
1752 * Note this routine inserts the subdirectories found between the entry being
1753 * processed, and any other directory still to be processed, mimicking what
1754 * Windows does
1756 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1757 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1758 WCHAR fullitem[MAX_PATH];
1759 WIN32_FIND_DATAW fd;
1760 HANDLE hff;
1762 /* Build a generic search and add all directories on the list of directories
1763 still to walk */
1764 strcpyW(fullitem, dirsToWalk->dirName);
1765 strcatW(fullitem, slashstarW);
1766 hff = FindFirstFileW(fullitem, &fd);
1767 if (hff != INVALID_HANDLE_VALUE) {
1768 do {
1769 WINE_TRACE("Looking for subdirectories\n");
1770 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1771 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1772 (strcmpW(fd.cFileName, dotW) != 0))
1774 /* Allocate memory, add to list */
1775 DIRECTORY_STACK *toWalk = heap_alloc(sizeof(DIRECTORY_STACK));
1776 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1777 toWalk->next = remainingDirs->next;
1778 remainingDirs->next = toWalk;
1779 remainingDirs = toWalk;
1780 toWalk->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1781 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1782 strcatW(toWalk->dirName, slashW);
1783 strcatW(toWalk->dirName, fd.cFileName);
1784 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1785 toWalk, toWalk->next);
1787 } while (FindNextFileW(hff, &fd) != 0);
1788 WINE_TRACE("Finished adding all subdirectories\n");
1789 FindClose (hff);
1793 /**************************************************************************
1794 * WCMD_for_nexttoken
1796 * Parse the token= line, identifying the next highest number not processed
1797 * so far. Count how many tokens are referred (including duplicates) and
1798 * optionally return that, plus optionally indicate if the tokens= line
1799 * ends in a star.
1801 * Parameters:
1802 * lasttoken [I] - Identifies the token index of the last one
1803 * returned so far (-1 used for first loop)
1804 * tokenstr [I] - The specified tokens= line
1805 * firstCmd [O] - Optionally indicate how many tokens are listed
1806 * doAll [O] - Optionally indicate if line ends with *
1807 * duplicates [O] - Optionally indicate if there is any evidence of
1808 * overlaying tokens in the string
1809 * Note the caller should keep a running track of duplicates as the tokens
1810 * are recursively passed. If any have duplicates, then the * token should
1811 * not be honoured.
1813 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1814 int *totalfound, BOOL *doall,
1815 BOOL *duplicates)
1817 WCHAR *pos = tokenstr;
1818 int nexttoken = -1;
1820 if (totalfound) *totalfound = 0;
1821 if (doall) *doall = FALSE;
1822 if (duplicates) *duplicates = FALSE;
1824 WINE_TRACE("Find next token after %d in %s was %d\n", lasttoken,
1825 wine_dbgstr_w(tokenstr), nexttoken);
1827 /* Loop through the token string, parsing it. Valid syntax is:
1828 token=m or x-y with comma delimiter and optionally * to finish*/
1829 while (*pos) {
1830 int nextnumber1, nextnumber2 = -1;
1831 WCHAR *nextchar;
1833 /* Get the next number */
1834 nextnumber1 = strtoulW(pos, &nextchar, 10);
1836 /* If it is followed by a minus, it's a range, so get the next one as well */
1837 if (*nextchar == '-') {
1838 nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1840 /* We want to return the lowest number that is higher than lasttoken
1841 but only if range is positive */
1842 if (nextnumber2 >= nextnumber1 &&
1843 lasttoken < nextnumber2) {
1845 int nextvalue;
1846 if (nexttoken == -1) {
1847 nextvalue = max(nextnumber1, (lasttoken+1));
1848 } else {
1849 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1852 /* Flag if duplicates identified */
1853 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1855 nexttoken = nextvalue;
1858 /* Update the running total for the whole range */
1859 if (nextnumber2 >= nextnumber1 && totalfound) {
1860 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1863 } else {
1864 if (totalfound) (*totalfound)++;
1866 /* See if the number found is one we have already seen */
1867 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1869 /* We want to return the lowest number that is higher than lasttoken */
1870 if (lasttoken < nextnumber1 &&
1871 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1872 nexttoken = nextnumber1;
1877 /* Remember if it is followed by a star, and if it is indicate a need to
1878 show all tokens, unless a duplicate has been found */
1879 if (*nextchar == '*') {
1880 if (doall) *doall = TRUE;
1881 if (totalfound) (*totalfound)++;
1884 /* Step on to the next character */
1885 pos = nextchar;
1886 if (*pos) pos++;
1889 /* Return result */
1890 if (nexttoken == -1) nexttoken = lasttoken;
1891 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1892 if (totalfound) WINE_TRACE("Found total tokens in total %d\n", *totalfound);
1893 if (doall && *doall) WINE_TRACE("Request for all tokens found\n");
1894 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1895 return nexttoken;
1898 /**************************************************************************
1899 * WCMD_parse_line
1901 * When parsing file or string contents (for /f), once the string to parse
1902 * has been identified, handle the various options and call the do part
1903 * if appropriate.
1905 * Parameters:
1906 * cmdStart [I] - Identifies the list of commands making up the
1907 * for loop body (especially if brackets in use)
1908 * firstCmd [I] - The textual start of the command after the DO
1909 * which is within the first item of cmdStart
1910 * cmdEnd [O] - Identifies where to continue after the DO
1911 * variable [I] - The variable identified on the for line
1912 * buffer [I] - The string to parse
1913 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1914 * forf_skip [I/O] - How many lines to skip first
1915 * forf_eol [I] - The 'end of line' (comment) character
1916 * forf_delims [I] - The delimiters to use when breaking the string apart
1917 * forf_tokens [I] - The tokens to use when breaking the string apart
1919 static void WCMD_parse_line(CMD_LIST *cmdStart,
1920 const WCHAR *firstCmd,
1921 CMD_LIST **cmdEnd,
1922 const WCHAR variable,
1923 WCHAR *buffer,
1924 BOOL *doExecuted,
1925 int *forf_skip,
1926 WCHAR forf_eol,
1927 WCHAR *forf_delims,
1928 WCHAR *forf_tokens) {
1930 WCHAR *parm;
1931 FOR_CONTEXT oldcontext;
1932 int varidx, varoffset;
1933 int nexttoken, lasttoken = -1;
1934 BOOL starfound = FALSE;
1935 BOOL thisduplicate = FALSE;
1936 BOOL anyduplicates = FALSE;
1937 int totalfound;
1939 /* Skip lines if requested */
1940 if (*forf_skip) {
1941 (*forf_skip)--;
1942 return;
1945 /* Save away any existing for variable context (e.g. nested for loops) */
1946 oldcontext = forloopcontext;
1948 /* Extract the parameters based on the tokens= value (There will always
1949 be some value, as if it is not supplied, it defaults to tokens=1).
1950 Rough logic:
1951 Count how many tokens are named in the line, identify the lowest
1952 Empty (set to null terminated string) that number of named variables
1953 While lasttoken != nextlowest
1954 %letter = parameter number 'nextlowest'
1955 letter++ (if >26 or >52 abort)
1956 Go through token= string finding next lowest number
1957 If token ends in * set %letter = raw position of token(nextnumber+1)
1959 lasttoken = -1;
1960 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1961 NULL, &thisduplicate);
1962 varidx = FOR_VAR_IDX(variable);
1964 /* Empty out variables */
1965 for (varoffset=0;
1966 varidx >= 0 && varoffset<totalfound && ((varidx+varoffset)%26);
1967 varoffset++) {
1968 forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
1969 /* Stop if we walk beyond z or Z */
1970 if (((varidx+varoffset) % 26) == 0) break;
1973 /* Loop extracting the tokens */
1974 varoffset = 0;
1975 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1976 while (varidx >= 0 && (nexttoken > lasttoken)) {
1977 anyduplicates |= thisduplicate;
1979 /* Extract the token number requested and set into the next variable context */
1980 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, FALSE, FALSE, forf_delims);
1981 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1982 varidx + varoffset, wine_dbgstr_w(parm));
1983 if (varidx >=0) {
1984 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1985 varoffset++;
1986 if (((varidx + varoffset) %26) == 0) break;
1989 /* Find the next token */
1990 lasttoken = nexttoken;
1991 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1992 &starfound, &thisduplicate);
1995 /* If all the rest of the tokens were requested, and there is still space in
1996 the variable range, write them now */
1997 if (!anyduplicates && starfound && varidx >= 0 && ((varidx+varoffset) % 26)) {
1998 nexttoken++;
1999 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
2000 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2001 varidx + varoffset, wine_dbgstr_w(parm));
2002 forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2005 /* Execute the body of the foor loop with these values */
2006 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
2007 CMD_LIST *thisCmdStart = cmdStart;
2008 *doExecuted = TRUE;
2009 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
2010 *cmdEnd = thisCmdStart;
2013 /* Free the duplicated strings, and restore the context */
2014 if (varidx >=0) {
2015 int i;
2016 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
2017 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
2018 (forloopcontext.variable[i] != nullW)) {
2019 heap_free(forloopcontext.variable[i]);
2024 /* Restore the original for variable contextx */
2025 forloopcontext = oldcontext;
2028 /**************************************************************************
2029 * WCMD_forf_getinputhandle
2031 * Return a file handle which can be used for reading the input lines,
2032 * either to a specific file (which may be quote delimited as we have to
2033 * read the parameters in raw mode) or to a command which we need to
2034 * execute. The command being executed runs in its own shell and stores
2035 * its data in a temporary file.
2037 * Parameters:
2038 * usebackq [I] - Indicates whether usebackq is in effect or not
2039 * itemStr [I] - The item to be handled, either a filename or
2040 * whole command string to execute
2041 * iscmd [I] - Identifies whether this is a command or not
2043 * Returns a file handle which can be used to read the input lines from.
2045 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2046 WCHAR temp_str[MAX_PATH];
2047 WCHAR temp_file[MAX_PATH];
2048 WCHAR temp_cmd[MAXSTRING];
2049 HANDLE hinput = INVALID_HANDLE_VALUE;
2050 static const WCHAR redirOutW[] = {'>','%','s','\0'};
2051 static const WCHAR cmdW[] = {'C','M','D','\0'};
2052 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
2053 '/','C',' ','"','%','s','"','\0'};
2055 /* Remove leading and trailing character */
2056 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2057 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2058 (!iscmd && (itemstr[0] == '"' && usebackq)))
2060 itemstr[strlenW(itemstr)-1] = 0x00;
2061 itemstr++;
2064 if (iscmd) {
2065 /* Get temp filename */
2066 GetTempPathW(sizeof(temp_str)/sizeof(WCHAR), temp_str);
2067 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
2069 /* Redirect output to the temporary file */
2070 wsprintfW(temp_str, redirOutW, temp_file);
2071 wsprintfW(temp_cmd, cmdslashcW, itemstr);
2072 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2073 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2074 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2076 /* Open the file, read line by line and process */
2077 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2078 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2080 } else {
2081 /* Open the file, read line by line and process */
2082 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2083 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2084 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2086 return hinput;
2089 /**************************************************************************
2090 * WCMD_for
2092 * Batch file loop processing.
2094 * On entry: cmdList contains the syntax up to the set
2095 * next cmdList and all in that bracket contain the set data
2096 * next cmdlist contains the DO cmd
2097 * following that is either brackets or && entries (as per if)
2101 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2103 WIN32_FIND_DATAW fd;
2104 HANDLE hff;
2105 int i;
2106 static const WCHAR inW[] = {'i','n'};
2107 static const WCHAR doW[] = {'d','o'};
2108 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2109 WCHAR variable[4];
2110 int varidx = -1;
2111 WCHAR *oldvariablevalue;
2112 WCHAR *firstCmd;
2113 int thisDepth;
2114 WCHAR optionsRoot[MAX_PATH];
2115 DIRECTORY_STACK *dirsToWalk = NULL;
2116 BOOL expandDirs = FALSE;
2117 BOOL useNumbers = FALSE;
2118 BOOL doFileset = FALSE;
2119 BOOL doRecurse = FALSE;
2120 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2121 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2122 int itemNum;
2123 CMD_LIST *thisCmdStart;
2124 int parameterNo = 0;
2125 WCHAR forf_eol = 0;
2126 int forf_skip = 0;
2127 WCHAR forf_delims[256];
2128 WCHAR forf_tokens[MAXSTRING];
2129 BOOL forf_usebackq = FALSE;
2131 /* Handle optional qualifiers (multiple are allowed) */
2132 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2134 optionsRoot[0] = 0;
2135 while (thisArg && *thisArg == '/') {
2136 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2137 thisArg++;
2138 switch (toupperW(*thisArg)) {
2139 case 'D': expandDirs = TRUE; break;
2140 case 'L': useNumbers = TRUE; break;
2142 /* Recursive is special case - /R can have an optional path following it */
2143 /* filenamesets are another special case - /F can have an optional options following it */
2144 case 'R':
2145 case 'F':
2147 /* When recursing directories, use current directory as the starting point unless
2148 subsequently overridden */
2149 doRecurse = (toupperW(*thisArg) == 'R');
2150 if (doRecurse) GetCurrentDirectoryW(sizeof(optionsRoot)/sizeof(WCHAR), optionsRoot);
2152 doFileset = (toupperW(*thisArg) == 'F');
2154 /* Retrieve next parameter to see if is root/options (raw form required
2155 with for /f, or unquoted in for /r) */
2156 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2158 /* Next parm is either qualifier, path/options or variable -
2159 only care about it if it is the path/options */
2160 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2161 parameterNo++;
2162 strcpyW(optionsRoot, thisArg);
2164 break;
2166 default:
2167 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2170 /* Step to next token */
2171 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2174 /* Ensure line continues with variable */
2175 if (!*thisArg || *thisArg != '%') {
2176 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2177 return;
2180 /* With for /f parse the options if provided */
2181 if (doFileset) {
2182 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2183 forf_delims, forf_tokens, &forf_usebackq))
2185 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2186 return;
2189 /* Set up the list of directories to recurse if we are going to */
2190 } else if (doRecurse) {
2191 /* Allocate memory, add to list */
2192 dirsToWalk = heap_alloc(sizeof(DIRECTORY_STACK));
2193 dirsToWalk->next = NULL;
2194 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2195 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2198 /* Variable should follow */
2199 strcpyW(variable, thisArg);
2200 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2201 varidx = FOR_VAR_IDX(variable[1]);
2203 /* Ensure line continues with IN */
2204 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2205 if (!thisArg
2206 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2207 thisArg, sizeof(inW)/sizeof(inW[0]), inW,
2208 sizeof(inW)/sizeof(inW[0])) == CSTR_EQUAL)) {
2209 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2210 return;
2213 /* Save away where the set of data starts and the variable */
2214 thisDepth = (*cmdList)->bracketDepth;
2215 *cmdList = (*cmdList)->nextcommand;
2216 setStart = (*cmdList);
2218 /* Skip until the close bracket */
2219 WINE_TRACE("Searching %p as the set\n", *cmdList);
2220 while (*cmdList &&
2221 (*cmdList)->command != NULL &&
2222 (*cmdList)->bracketDepth > thisDepth) {
2223 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2224 *cmdList = (*cmdList)->nextcommand;
2227 /* Skip the close bracket, if there is one */
2228 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2230 /* Syntax error if missing close bracket, or nothing following it
2231 and once we have the complete set, we expect a DO */
2232 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2233 if ((*cmdList == NULL)
2234 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
2236 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2237 return;
2240 cmdEnd = *cmdList;
2242 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2243 mode, or once for the rest of the time. */
2244 do {
2246 /* Save away the starting position for the commands (and offset for the
2247 first one) */
2248 cmdStart = *cmdList;
2249 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2250 itemNum = 0;
2252 /* If we are recursing directories (ie /R), add all sub directories now, then
2253 prefix the root when searching for the item */
2254 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2256 thisSet = setStart;
2257 /* Loop through all set entries */
2258 while (thisSet &&
2259 thisSet->command != NULL &&
2260 thisSet->bracketDepth >= thisDepth) {
2262 /* Loop through all entries on the same line */
2263 WCHAR *item;
2264 WCHAR *itemStart;
2265 WCHAR buffer[MAXSTRING];
2267 WINE_TRACE("Processing for set %p\n", thisSet);
2268 i = 0;
2269 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2272 * If the parameter within the set has a wildcard then search for matching files
2273 * otherwise do a literal substitution.
2275 static const WCHAR wildcards[] = {'*','?','\0'};
2276 thisCmdStart = cmdStart;
2278 itemNum++;
2279 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2281 if (!useNumbers && !doFileset) {
2282 WCHAR fullitem[MAX_PATH];
2283 int prefixlen = 0;
2285 /* Now build the item to use / search for in the specified directory,
2286 as it is fully qualified in the /R case */
2287 if (dirsToWalk) {
2288 strcpyW(fullitem, dirsToWalk->dirName);
2289 strcatW(fullitem, slashW);
2290 strcatW(fullitem, item);
2291 } else {
2292 WCHAR *prefix = strrchrW(item, '\\');
2293 if (prefix) prefixlen = (prefix - item) + 1;
2294 strcpyW(fullitem, item);
2297 if (strpbrkW (fullitem, wildcards)) {
2298 hff = FindFirstFileW(fullitem, &fd);
2299 if (hff != INVALID_HANDLE_VALUE) {
2300 do {
2301 BOOL isDirectory = FALSE;
2303 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2305 /* Handle as files or dirs appropriately, but ignore . and .. */
2306 if (isDirectory == expandDirs &&
2307 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2308 (strcmpW(fd.cFileName, dotW) != 0))
2310 thisCmdStart = cmdStart;
2311 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2313 if (doRecurse) {
2314 strcpyW(fullitem, dirsToWalk->dirName);
2315 strcatW(fullitem, slashW);
2316 strcatW(fullitem, fd.cFileName);
2317 } else {
2318 if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
2319 fullitem[prefixlen] = 0x00;
2320 strcatW(fullitem, fd.cFileName);
2322 doExecuted = TRUE;
2324 /* Save away any existing for variable context (e.g. nested for loops)
2325 and restore it after executing the body of this for loop */
2326 if (varidx >= 0) {
2327 oldvariablevalue = forloopcontext.variable[varidx];
2328 forloopcontext.variable[varidx] = fullitem;
2330 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2331 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2333 cmdEnd = thisCmdStart;
2335 } while (FindNextFileW(hff, &fd) != 0);
2336 FindClose (hff);
2338 } else {
2339 doExecuted = TRUE;
2341 /* Save away any existing for variable context (e.g. nested for loops)
2342 and restore it after executing the body of this for loop */
2343 if (varidx >= 0) {
2344 oldvariablevalue = forloopcontext.variable[varidx];
2345 forloopcontext.variable[varidx] = fullitem;
2347 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2348 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2350 cmdEnd = thisCmdStart;
2353 } else if (useNumbers) {
2354 /* Convert the first 3 numbers to signed longs and save */
2355 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2356 /* else ignore them! */
2358 /* Filesets - either a list of files, or a command to run and parse the output */
2359 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2360 (forf_usebackq && *itemStart != '\''))) {
2362 HANDLE input;
2363 WCHAR *itemparm;
2365 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2366 wine_dbgstr_w(item));
2368 /* If backquote or single quote, we need to launch that command
2369 and parse the results - use a temporary file */
2370 if ((forf_usebackq && *itemStart == '`') ||
2371 (!forf_usebackq && *itemStart == '\'')) {
2373 /* Use itemstart because the command is the whole set, not just the first token */
2374 itemparm = itemStart;
2375 } else {
2377 /* Use item because the file to process is just the first item in the set */
2378 itemparm = item;
2380 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2382 /* Process the input file */
2383 if (input == INVALID_HANDLE_VALUE) {
2384 WCMD_print_error ();
2385 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2386 errorlevel = 1;
2387 return; /* FOR loop aborts at first failure here */
2389 } else {
2391 /* Read line by line until end of file */
2392 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
2393 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2394 &forf_skip, forf_eol, forf_delims, forf_tokens);
2395 buffer[0] = 0;
2397 CloseHandle (input);
2400 /* When we have processed the item as a whole command, abort future set processing */
2401 if (itemparm==itemStart) {
2402 thisSet = NULL;
2403 break;
2406 /* Filesets - A string literal */
2407 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2408 (forf_usebackq && *itemStart == '\''))) {
2410 /* Remove leading and trailing character, ready to parse with delims= delimiters
2411 Note that the last quote is removed from the set and the string terminates
2412 there to mimic windows */
2413 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2414 if (strend) {
2415 *strend = 0x00;
2416 itemStart++;
2419 /* Copy the item away from the global buffer used by WCMD_parameter */
2420 strcpyW(buffer, itemStart);
2421 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2422 &forf_skip, forf_eol, forf_delims, forf_tokens);
2424 /* Only one string can be supplied in the whole set, abort future set processing */
2425 thisSet = NULL;
2426 break;
2429 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2430 i++;
2433 /* Move onto the next set line */
2434 if (thisSet) thisSet = thisSet->nextcommand;
2437 /* If /L is provided, now run the for loop */
2438 if (useNumbers) {
2439 WCHAR thisNum[20];
2440 static const WCHAR fmt[] = {'%','d','\0'};
2442 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2443 numbers[0], numbers[2], numbers[1]);
2444 for (i=numbers[0];
2445 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2446 i=i + numbers[1]) {
2448 sprintfW(thisNum, fmt, i);
2449 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2451 thisCmdStart = cmdStart;
2452 doExecuted = TRUE;
2454 /* Save away any existing for variable context (e.g. nested for loops)
2455 and restore it after executing the body of this for loop */
2456 if (varidx >= 0) {
2457 oldvariablevalue = forloopcontext.variable[varidx];
2458 forloopcontext.variable[varidx] = thisNum;
2460 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2461 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2463 cmdEnd = thisCmdStart;
2466 /* If we are walking directories, move on to any which remain */
2467 if (dirsToWalk != NULL) {
2468 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2469 heap_free(dirsToWalk->dirName);
2470 heap_free(dirsToWalk);
2471 dirsToWalk = nextDir;
2472 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2473 wine_dbgstr_w(dirsToWalk->dirName));
2474 else WINE_TRACE("Finished all directories.\n");
2477 } while (dirsToWalk != NULL);
2479 /* Now skip over the do part if we did not perform the for loop so far.
2480 We store in cmdEnd the next command after the do block, but we only
2481 know this if something was run. If it has not been, we need to calculate
2482 it. */
2483 if (!doExecuted) {
2484 thisCmdStart = cmdStart;
2485 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2486 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2487 cmdEnd = thisCmdStart;
2490 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2491 all processing, OR it should be pointing to the end of && processing OR
2492 it should be pointing at the NULL end of bracket for the DO. The return
2493 value needs to be the NEXT command to execute, which it either is, or
2494 we need to step over the closing bracket */
2495 *cmdList = cmdEnd;
2496 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2499 /**************************************************************************
2500 * WCMD_give_help
2502 * Simple on-line help. Help text is stored in the resource file.
2505 void WCMD_give_help (const WCHAR *args)
2507 size_t i;
2509 args = WCMD_skip_leading_spaces((WCHAR*) args);
2510 if (strlenW(args) == 0) {
2511 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2513 else {
2514 /* Display help message for builtin commands */
2515 for (i=0; i<=WCMD_EXIT; i++) {
2516 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2517 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2518 WCMD_output_asis (WCMD_LoadMessage(i));
2519 return;
2522 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2523 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
2524 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2525 args, -1, externals[i], -1) == CSTR_EQUAL) {
2526 WCHAR cmd[128];
2527 static const WCHAR helpW[] = {' ', '/','?','\0'};
2528 strcpyW(cmd, args);
2529 strcatW(cmd, helpW);
2530 WCMD_run_program(cmd, FALSE);
2531 return;
2534 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2536 return;
2539 /****************************************************************************
2540 * WCMD_go_to
2542 * Batch file jump instruction. Not the most efficient algorithm ;-)
2543 * Prints error message if the specified label cannot be found - the file pointer is
2544 * then at EOF, effectively stopping the batch file.
2545 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2548 void WCMD_goto (CMD_LIST **cmdList) {
2550 WCHAR string[MAX_PATH];
2551 WCHAR *labelend = NULL;
2552 const WCHAR labelEndsW[] = {'>','<','|','&',' ',':','\t','\0'};
2554 /* Do not process any more parts of a processed multipart or multilines command */
2555 if (cmdList) *cmdList = NULL;
2557 if (context != NULL) {
2558 WCHAR *paramStart = param1, *str;
2559 static const WCHAR eofW[] = {':','e','o','f','\0'};
2561 if (param1[0] == 0x00) {
2562 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2563 return;
2566 /* Handle special :EOF label */
2567 if (lstrcmpiW (eofW, param1) == 0) {
2568 context -> skip_rest = TRUE;
2569 return;
2572 /* Support goto :label as well as goto label plus remove trailing chars */
2573 if (*paramStart == ':') paramStart++;
2574 labelend = strpbrkW(paramStart, labelEndsW);
2575 if (labelend) *labelend = 0x00;
2576 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
2578 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2579 while (*paramStart &&
2580 WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
2581 str = string;
2583 /* Ignore leading whitespace or no-echo character */
2584 while (*str=='@' || isspaceW (*str)) str++;
2586 /* If the first real character is a : then this is a label */
2587 if (*str == ':') {
2588 str++;
2590 /* Skip spaces between : and label */
2591 while (isspaceW (*str)) str++;
2592 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
2594 /* Label ends at whitespace or redirection characters */
2595 labelend = strpbrkW(str, labelEndsW);
2596 if (labelend) *labelend = 0x00;
2597 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
2599 if (lstrcmpiW (str, paramStart) == 0) return;
2602 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2603 context -> skip_rest = TRUE;
2605 return;
2608 /*****************************************************************************
2609 * WCMD_pushd
2611 * Push a directory onto the stack
2614 void WCMD_pushd (const WCHAR *args)
2616 struct env_stack *curdir;
2617 WCHAR *thisdir;
2618 static const WCHAR parmD[] = {'/','D','\0'};
2620 if (strchrW(args, '/') != NULL) {
2621 SetLastError(ERROR_INVALID_PARAMETER);
2622 WCMD_print_error();
2623 return;
2626 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2627 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2628 if( !curdir || !thisdir ) {
2629 LocalFree(curdir);
2630 LocalFree(thisdir);
2631 WINE_ERR ("out of memory\n");
2632 return;
2635 /* Change directory using CD code with /D parameter */
2636 strcpyW(quals, parmD);
2637 GetCurrentDirectoryW (1024, thisdir);
2638 errorlevel = 0;
2639 WCMD_setshow_default(args);
2640 if (errorlevel) {
2641 LocalFree(curdir);
2642 LocalFree(thisdir);
2643 return;
2644 } else {
2645 curdir -> next = pushd_directories;
2646 curdir -> strings = thisdir;
2647 if (pushd_directories == NULL) {
2648 curdir -> u.stackdepth = 1;
2649 } else {
2650 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2652 pushd_directories = curdir;
2657 /*****************************************************************************
2658 * WCMD_popd
2660 * Pop a directory from the stack
2663 void WCMD_popd (void) {
2664 struct env_stack *temp = pushd_directories;
2666 if (!pushd_directories)
2667 return;
2669 /* pop the old environment from the stack, and make it the current dir */
2670 pushd_directories = temp->next;
2671 SetCurrentDirectoryW(temp->strings);
2672 LocalFree (temp->strings);
2673 LocalFree (temp);
2676 /*******************************************************************
2677 * evaluate_if_comparison
2679 * Evaluates an "if" comparison operation
2681 * PARAMS
2682 * leftOperand [I] left operand, non NULL
2683 * operator [I] "if" binary comparison operator, non NULL
2684 * rightOperand [I] right operand, non NULL
2685 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2687 * RETURNS
2688 * Success: 1 if operator applied to the operands evaluates to TRUE
2689 * 0 if operator applied to the operands evaluates to FALSE
2690 * Failure: -1 if operator is not recognized
2692 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2693 const WCHAR *rightOperand, int caseInsensitive)
2695 WCHAR *endptr_leftOp, *endptr_rightOp;
2696 long int leftOperand_int, rightOperand_int;
2697 BOOL int_operands;
2698 static const WCHAR lssW[] = {'l','s','s','\0'};
2699 static const WCHAR leqW[] = {'l','e','q','\0'};
2700 static const WCHAR equW[] = {'e','q','u','\0'};
2701 static const WCHAR neqW[] = {'n','e','q','\0'};
2702 static const WCHAR geqW[] = {'g','e','q','\0'};
2703 static const WCHAR gtrW[] = {'g','t','r','\0'};
2705 /* == is a special case, as it always compares strings */
2706 if (!lstrcmpiW(operator, eqeqW))
2707 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2708 : lstrcmpW (leftOperand, rightOperand) == 0;
2710 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2711 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2712 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2713 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2715 /* Perform actual (integer or string) comparison */
2716 if (!lstrcmpiW(operator, lssW)) {
2717 if (int_operands)
2718 return leftOperand_int < rightOperand_int;
2719 else
2720 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2721 : lstrcmpW (leftOperand, rightOperand) < 0;
2724 if (!lstrcmpiW(operator, leqW)) {
2725 if (int_operands)
2726 return leftOperand_int <= rightOperand_int;
2727 else
2728 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2729 : lstrcmpW (leftOperand, rightOperand) <= 0;
2732 if (!lstrcmpiW(operator, equW)) {
2733 if (int_operands)
2734 return leftOperand_int == rightOperand_int;
2735 else
2736 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2737 : lstrcmpW (leftOperand, rightOperand) == 0;
2740 if (!lstrcmpiW(operator, neqW)) {
2741 if (int_operands)
2742 return leftOperand_int != rightOperand_int;
2743 else
2744 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2745 : lstrcmpW (leftOperand, rightOperand) != 0;
2748 if (!lstrcmpiW(operator, geqW)) {
2749 if (int_operands)
2750 return leftOperand_int >= rightOperand_int;
2751 else
2752 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2753 : lstrcmpW (leftOperand, rightOperand) >= 0;
2756 if (!lstrcmpiW(operator, gtrW)) {
2757 if (int_operands)
2758 return leftOperand_int > rightOperand_int;
2759 else
2760 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2761 : lstrcmpW (leftOperand, rightOperand) > 0;
2764 return -1;
2767 /****************************************************************************
2768 * WCMD_if
2770 * Batch file conditional.
2772 * On entry, cmdlist will point to command containing the IF, and optionally
2773 * the first command to execute (if brackets not found)
2774 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2775 * If ('s were found, execute all within that bracket
2776 * Command may optionally be followed by an ELSE - need to skip instructions
2777 * in the else using the same logic
2779 * FIXME: Much more syntax checking needed!
2781 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2783 int negate; /* Negate condition */
2784 int test; /* Condition evaluation result */
2785 WCHAR condition[MAX_PATH], *command;
2786 static const WCHAR notW[] = {'n','o','t','\0'};
2787 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2788 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2789 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2790 static const WCHAR parmI[] = {'/','I','\0'};
2791 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2793 negate = !lstrcmpiW(param1,notW);
2794 strcpyW(condition, (negate ? param2 : param1));
2795 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2797 if (!lstrcmpiW (condition, errlvlW)) {
2798 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2799 WCHAR *endptr;
2800 long int param_int = strtolW(param, &endptr, 10);
2801 if (*endptr) goto syntax_err;
2802 test = ((long int)errorlevel >= param_int);
2803 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2805 else if (!lstrcmpiW (condition, existW)) {
2806 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE))
2807 != INVALID_FILE_ATTRIBUTES);
2808 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2810 else if (!lstrcmpiW (condition, defdW)) {
2811 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2812 NULL, 0) > 0);
2813 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2815 else { /* comparison operation */
2816 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2817 WCHAR *paramStart;
2819 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, &paramStart, TRUE, FALSE));
2820 if (!*leftOperand)
2821 goto syntax_err;
2823 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2824 p = paramStart + strlenW(leftOperand);
2825 while (*p == ' ' || *p == '\t')
2826 p++;
2828 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2829 strcpyW(operator, eqeqW);
2830 else {
2831 strcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2832 if (!*operator) goto syntax_err;
2834 p += strlenW(operator);
2836 strcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2837 if (!*rightOperand)
2838 goto syntax_err;
2840 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2841 if (test == -1)
2842 goto syntax_err;
2844 p = paramStart + strlenW(rightOperand);
2845 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2848 /* Process rest of IF statement which is on the same line
2849 Note: This may process all or some of the cmdList (eg a GOTO) */
2850 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2851 return;
2853 syntax_err:
2854 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2857 /****************************************************************************
2858 * WCMD_move
2860 * Move a file, directory tree or wildcarded set of files.
2863 void WCMD_move (void)
2865 BOOL status;
2866 WIN32_FIND_DATAW fd;
2867 HANDLE hff;
2868 WCHAR input[MAX_PATH];
2869 WCHAR output[MAX_PATH];
2870 WCHAR drive[10];
2871 WCHAR dir[MAX_PATH];
2872 WCHAR fname[MAX_PATH];
2873 WCHAR ext[MAX_PATH];
2875 if (param1[0] == 0x00) {
2876 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2877 return;
2880 /* If no destination supplied, assume current directory */
2881 if (param2[0] == 0x00) {
2882 strcpyW(param2, dotW);
2885 /* If 2nd parm is directory, then use original filename */
2886 /* Convert partial path to full path */
2887 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
2888 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
2889 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2890 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2892 /* Split into components */
2893 WCMD_splitpath(input, drive, dir, fname, ext);
2895 hff = FindFirstFileW(input, &fd);
2896 if (hff == INVALID_HANDLE_VALUE)
2897 return;
2899 do {
2900 WCHAR dest[MAX_PATH];
2901 WCHAR src[MAX_PATH];
2902 DWORD attribs;
2903 BOOL ok = TRUE;
2905 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2907 /* Build src & dest name */
2908 strcpyW(src, drive);
2909 strcatW(src, dir);
2911 /* See if dest is an existing directory */
2912 attribs = GetFileAttributesW(output);
2913 if (attribs != INVALID_FILE_ATTRIBUTES &&
2914 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2915 strcpyW(dest, output);
2916 strcatW(dest, slashW);
2917 strcatW(dest, fd.cFileName);
2918 } else {
2919 strcpyW(dest, output);
2922 strcatW(src, fd.cFileName);
2924 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2925 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2927 /* If destination exists, prompt unless /Y supplied */
2928 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2929 BOOL force = FALSE;
2930 WCHAR copycmd[MAXSTRING];
2931 DWORD len;
2933 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2934 if (strstrW (quals, parmNoY))
2935 force = FALSE;
2936 else if (strstrW (quals, parmY))
2937 force = TRUE;
2938 else {
2939 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
2940 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
2941 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
2942 && ! lstrcmpiW (copycmd, parmY));
2945 /* Prompt if overwriting */
2946 if (!force) {
2947 WCHAR* question;
2949 /* Ask for confirmation */
2950 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
2951 ok = WCMD_ask_confirm(question, FALSE, NULL);
2952 LocalFree(question);
2954 /* So delete the destination prior to the move */
2955 if (ok) {
2956 if (!DeleteFileW(dest)) {
2957 WCMD_print_error ();
2958 errorlevel = 1;
2959 ok = FALSE;
2965 if (ok) {
2966 status = MoveFileW(src, dest);
2967 } else {
2968 status = TRUE;
2971 if (!status) {
2972 WCMD_print_error ();
2973 errorlevel = 1;
2975 } while (FindNextFileW(hff, &fd) != 0);
2977 FindClose(hff);
2980 /****************************************************************************
2981 * WCMD_pause
2983 * Suspend execution of a batch script until a key is typed
2986 void WCMD_pause (void)
2988 DWORD oldmode;
2989 BOOL have_console;
2990 DWORD count;
2991 WCHAR key;
2992 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
2994 have_console = GetConsoleMode(hIn, &oldmode);
2995 if (have_console)
2996 SetConsoleMode(hIn, 0);
2998 WCMD_output_asis(anykey);
2999 WCMD_ReadFile(hIn, &key, 1, &count);
3000 if (have_console)
3001 SetConsoleMode(hIn, oldmode);
3004 /****************************************************************************
3005 * WCMD_remove_dir
3007 * Delete a directory.
3010 void WCMD_remove_dir (WCHAR *args) {
3012 int argno = 0;
3013 int argsProcessed = 0;
3014 WCHAR *argN = args;
3015 static const WCHAR parmS[] = {'/','S','\0'};
3016 static const WCHAR parmQ[] = {'/','Q','\0'};
3018 /* Loop through all args */
3019 while (argN) {
3020 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3021 if (argN && argN[0] != '/') {
3022 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
3023 wine_dbgstr_w(quals));
3024 argsProcessed++;
3026 /* If subdirectory search not supplied, just try to remove
3027 and report error if it fails (eg if it contains a file) */
3028 if (strstrW (quals, parmS) == NULL) {
3029 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
3031 /* Otherwise use ShFileOp to recursively remove a directory */
3032 } else {
3034 SHFILEOPSTRUCTW lpDir;
3036 /* Ask first */
3037 if (strstrW (quals, parmQ) == NULL) {
3038 BOOL ok;
3039 WCHAR question[MAXSTRING];
3040 static const WCHAR fmt[] = {'%','s',' ','\0'};
3042 /* Ask for confirmation */
3043 wsprintfW(question, fmt, thisArg);
3044 ok = WCMD_ask_confirm(question, TRUE, NULL);
3046 /* Abort if answer is 'N' */
3047 if (!ok) return;
3050 /* Do the delete */
3051 lpDir.hwnd = NULL;
3052 lpDir.pTo = NULL;
3053 lpDir.pFrom = thisArg;
3054 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
3055 lpDir.wFunc = FO_DELETE;
3057 /* SHFileOperationW needs file list with a double null termination */
3058 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3060 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3065 /* Handle no valid args */
3066 if (argsProcessed == 0) {
3067 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3068 return;
3073 /****************************************************************************
3074 * WCMD_rename
3076 * Rename a file.
3079 void WCMD_rename (void)
3081 BOOL status;
3082 HANDLE hff;
3083 WIN32_FIND_DATAW fd;
3084 WCHAR input[MAX_PATH];
3085 WCHAR *dotDst = NULL;
3086 WCHAR drive[10];
3087 WCHAR dir[MAX_PATH];
3088 WCHAR fname[MAX_PATH];
3089 WCHAR ext[MAX_PATH];
3091 errorlevel = 0;
3093 /* Must be at least two args */
3094 if (param1[0] == 0x00 || param2[0] == 0x00) {
3095 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3096 errorlevel = 1;
3097 return;
3100 /* Destination cannot contain a drive letter or directory separator */
3101 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
3102 SetLastError(ERROR_INVALID_PARAMETER);
3103 WCMD_print_error();
3104 errorlevel = 1;
3105 return;
3108 /* Convert partial path to full path */
3109 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
3110 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3111 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3112 dotDst = strchrW(param2, '.');
3114 /* Split into components */
3115 WCMD_splitpath(input, drive, dir, fname, ext);
3117 hff = FindFirstFileW(input, &fd);
3118 if (hff == INVALID_HANDLE_VALUE)
3119 return;
3121 do {
3122 WCHAR dest[MAX_PATH];
3123 WCHAR src[MAX_PATH];
3124 WCHAR *dotSrc = NULL;
3125 int dirLen;
3127 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3129 /* FIXME: If dest name or extension is *, replace with filename/ext
3130 part otherwise use supplied name. This supports:
3131 ren *.fred *.jim
3132 ren jim.* fred.* etc
3133 However, windows has a more complex algorithm supporting eg
3134 ?'s and *'s mid name */
3135 dotSrc = strchrW(fd.cFileName, '.');
3137 /* Build src & dest name */
3138 strcpyW(src, drive);
3139 strcatW(src, dir);
3140 strcpyW(dest, src);
3141 dirLen = strlenW(src);
3142 strcatW(src, fd.cFileName);
3144 /* Build name */
3145 if (param2[0] == '*') {
3146 strcatW(dest, fd.cFileName);
3147 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3148 } else {
3149 strcatW(dest, param2);
3150 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3153 /* Build Extension */
3154 if (dotDst && (*(dotDst+1)=='*')) {
3155 if (dotSrc) strcatW(dest, dotSrc);
3156 } else if (dotDst) {
3157 strcatW(dest, dotDst);
3160 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3161 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3163 status = MoveFileW(src, dest);
3165 if (!status) {
3166 WCMD_print_error ();
3167 errorlevel = 1;
3169 } while (FindNextFileW(hff, &fd) != 0);
3171 FindClose(hff);
3174 /*****************************************************************************
3175 * WCMD_dupenv
3177 * Make a copy of the environment.
3179 static WCHAR *WCMD_dupenv( const WCHAR *env )
3181 WCHAR *env_copy;
3182 int len;
3184 if( !env )
3185 return NULL;
3187 len = 0;
3188 while ( env[len] )
3189 len += (strlenW(&env[len]) + 1);
3191 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3192 if (!env_copy)
3194 WINE_ERR("out of memory\n");
3195 return env_copy;
3197 memcpy (env_copy, env, len*sizeof (WCHAR));
3198 env_copy[len] = 0;
3200 return env_copy;
3203 /*****************************************************************************
3204 * WCMD_setlocal
3206 * setlocal pushes the environment onto a stack
3207 * Save the environment as unicode so we don't screw anything up.
3209 void WCMD_setlocal (const WCHAR *s) {
3210 WCHAR *env;
3211 struct env_stack *env_copy;
3212 WCHAR cwd[MAX_PATH];
3213 BOOL newdelay;
3214 static const WCHAR ondelayW[] = {'E','N','A','B','L','E','D','E','L','A',
3215 'Y','E','D','E','X','P','A','N','S','I',
3216 'O','N','\0'};
3217 static const WCHAR offdelayW[] = {'D','I','S','A','B','L','E','D','E','L',
3218 'A','Y','E','D','E','X','P','A','N','S',
3219 'I','O','N','\0'};
3221 /* setlocal does nothing outside of batch programs */
3222 if (!context) return;
3224 /* DISABLEEXTENSIONS ignored */
3226 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3227 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3228 if (!strcmpiW(param1, ondelayW) || !strcmpiW(param2, ondelayW)) {
3229 newdelay = TRUE;
3230 } else if (!strcmpiW(param1, offdelayW) || !strcmpiW(param2, offdelayW)) {
3231 newdelay = FALSE;
3232 } else {
3233 newdelay = delayedsubst;
3235 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3237 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3238 if( !env_copy )
3240 WINE_ERR ("out of memory\n");
3241 return;
3244 env = GetEnvironmentStringsW ();
3245 env_copy->strings = WCMD_dupenv (env);
3246 if (env_copy->strings)
3248 env_copy->batchhandle = context->h;
3249 env_copy->next = saved_environment;
3250 env_copy->delayedsubst = delayedsubst;
3251 delayedsubst = newdelay;
3252 saved_environment = env_copy;
3254 /* Save the current drive letter */
3255 GetCurrentDirectoryW(MAX_PATH, cwd);
3256 env_copy->u.cwd = cwd[0];
3258 else
3259 LocalFree (env_copy);
3261 FreeEnvironmentStringsW (env);
3265 /*****************************************************************************
3266 * WCMD_endlocal
3268 * endlocal pops the environment off a stack
3269 * Note: When searching for '=', search from WCHAR position 1, to handle
3270 * special internal environment variables =C:, =D: etc
3272 void WCMD_endlocal (void) {
3273 WCHAR *env, *old, *p;
3274 struct env_stack *temp;
3275 int len, n;
3277 /* setlocal does nothing outside of batch programs */
3278 if (!context) return;
3280 /* setlocal needs a saved environment from within the same context (batch
3281 program) as it was saved in */
3282 if (!saved_environment || saved_environment->batchhandle != context->h)
3283 return;
3285 /* pop the old environment from the stack */
3286 temp = saved_environment;
3287 saved_environment = temp->next;
3289 /* delete the current environment, totally */
3290 env = GetEnvironmentStringsW ();
3291 old = WCMD_dupenv (env);
3292 len = 0;
3293 while (old[len]) {
3294 n = strlenW(&old[len]) + 1;
3295 p = strchrW(&old[len] + 1, '=');
3296 if (p)
3298 *p++ = 0;
3299 SetEnvironmentVariableW (&old[len], NULL);
3301 len += n;
3303 LocalFree (old);
3304 FreeEnvironmentStringsW (env);
3306 /* restore old environment */
3307 env = temp->strings;
3308 len = 0;
3309 delayedsubst = temp->delayedsubst;
3310 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3311 while (env[len]) {
3312 n = strlenW(&env[len]) + 1;
3313 p = strchrW(&env[len] + 1, '=');
3314 if (p)
3316 *p++ = 0;
3317 SetEnvironmentVariableW (&env[len], p);
3319 len += n;
3322 /* Restore current drive letter */
3323 if (IsCharAlphaW(temp->u.cwd)) {
3324 WCHAR envvar[4];
3325 WCHAR cwd[MAX_PATH];
3326 static const WCHAR fmt[] = {'=','%','c',':','\0'};
3328 wsprintfW(envvar, fmt, temp->u.cwd);
3329 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3330 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3331 SetCurrentDirectoryW(cwd);
3335 LocalFree (env);
3336 LocalFree (temp);
3339 /*****************************************************************************
3340 * WCMD_setshow_default
3342 * Set/Show the current default directory
3345 void WCMD_setshow_default (const WCHAR *args) {
3347 BOOL status;
3348 WCHAR string[1024];
3349 WCHAR cwd[1024];
3350 WCHAR *pos;
3351 WIN32_FIND_DATAW fd;
3352 HANDLE hff;
3353 static const WCHAR parmD[] = {'/','D','\0'};
3355 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3357 /* Skip /D and trailing whitespace if on the front of the command line */
3358 if (strlenW(args) >= 2 &&
3359 CompareStringW(LOCALE_USER_DEFAULT,
3360 NORM_IGNORECASE | SORT_STRINGSORT,
3361 args, 2, parmD, -1) == CSTR_EQUAL) {
3362 args += 2;
3363 while (*args && (*args==' ' || *args=='\t'))
3364 args++;
3367 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
3368 if (strlenW(args) == 0) {
3369 strcatW (cwd, newlineW);
3370 WCMD_output_asis (cwd);
3372 else {
3373 /* Remove any double quotes, which may be in the
3374 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3375 pos = string;
3376 while (*args) {
3377 if (*args != '"') *pos++ = *args;
3378 args++;
3380 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3381 pos--;
3382 *pos = 0x00;
3384 /* Search for appropriate directory */
3385 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3386 hff = FindFirstFileW(string, &fd);
3387 if (hff != INVALID_HANDLE_VALUE) {
3388 do {
3389 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3390 WCHAR fpath[MAX_PATH];
3391 WCHAR drive[10];
3392 WCHAR dir[MAX_PATH];
3393 WCHAR fname[MAX_PATH];
3394 WCHAR ext[MAX_PATH];
3395 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3397 /* Convert path into actual directory spec */
3398 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
3399 WCMD_splitpath(fpath, drive, dir, fname, ext);
3401 /* Rebuild path */
3402 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3403 break;
3405 } while (FindNextFileW(hff, &fd) != 0);
3406 FindClose(hff);
3409 /* Change to that directory */
3410 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3412 status = SetCurrentDirectoryW(string);
3413 if (!status) {
3414 errorlevel = 1;
3415 WCMD_print_error ();
3416 return;
3417 } else {
3419 /* Save away the actual new directory, to store as current location */
3420 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
3422 /* Restore old directory if drive letter would change, and
3423 CD x:\directory /D (or pushd c:\directory) not supplied */
3424 if ((strstrW(quals, parmD) == NULL) &&
3425 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3426 SetCurrentDirectoryW(cwd);
3430 /* Set special =C: type environment variable, for drive letter of
3431 change of directory, even if path was restored due to missing
3432 /D (allows changing drive letter when not resident on that
3433 drive */
3434 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3435 WCHAR env[4];
3436 strcpyW(env, equalW);
3437 memcpy(env+1, string, 2 * sizeof(WCHAR));
3438 env[3] = 0x00;
3439 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3440 SetEnvironmentVariableW(env, string);
3444 return;
3447 /****************************************************************************
3448 * WCMD_setshow_date
3450 * Set/Show the system date
3451 * FIXME: Can't change date yet
3454 void WCMD_setshow_date (void) {
3456 WCHAR curdate[64], buffer[64];
3457 DWORD count;
3458 static const WCHAR parmT[] = {'/','T','\0'};
3460 if (strlenW(param1) == 0) {
3461 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
3462 curdate, sizeof(curdate)/sizeof(WCHAR))) {
3463 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3464 if (strstrW (quals, parmT) == NULL) {
3465 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3466 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
3467 if (count > 2) {
3468 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3472 else WCMD_print_error ();
3474 else {
3475 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3479 /****************************************************************************
3480 * WCMD_compare
3481 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3482 * the equals sign.
3484 static int WCMD_compare( const void *a, const void *b )
3486 int r;
3487 const WCHAR * const *str_a = a, * const *str_b = b;
3488 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3489 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3490 if( r == CSTR_LESS_THAN ) return -1;
3491 if( r == CSTR_GREATER_THAN ) return 1;
3492 return 0;
3495 /****************************************************************************
3496 * WCMD_setshow_sortenv
3498 * sort variables into order for display
3499 * Optionally only display those who start with a stub
3500 * returns the count displayed
3502 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3504 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3505 const WCHAR **str;
3507 if (stub) stublen = strlenW(stub);
3509 /* count the number of strings, and the total length */
3510 while ( s[len] ) {
3511 len += (strlenW(&s[len]) + 1);
3512 count++;
3515 /* add the strings to an array */
3516 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3517 if( !str )
3518 return 0;
3519 str[0] = s;
3520 for( i=1; i<count; i++ )
3521 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3523 /* sort the array */
3524 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3526 /* print it */
3527 for( i=0; i<count; i++ ) {
3528 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3529 NORM_IGNORECASE | SORT_STRINGSORT,
3530 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3531 /* Don't display special internal variables */
3532 if (str[i][0] != '=') {
3533 WCMD_output_asis(str[i]);
3534 WCMD_output_asis(newlineW);
3535 displayedcount++;
3540 LocalFree( str );
3541 return displayedcount;
3544 /****************************************************************************
3545 * WCMD_getprecedence
3546 * Return the precedence of a particular operator
3548 static int WCMD_getprecedence(const WCHAR in)
3550 switch (in) {
3551 case '!':
3552 case '~':
3553 case OP_POSITIVE:
3554 case OP_NEGATIVE:
3555 return 8;
3556 case '*':
3557 case '/':
3558 case '%':
3559 return 7;
3560 case '+':
3561 case '-':
3562 return 6;
3563 case '<':
3564 case '>':
3565 return 5;
3566 case '&':
3567 return 4;
3568 case '^':
3569 return 3;
3570 case '|':
3571 return 2;
3572 case '=':
3573 case OP_ASSSIGNMUL:
3574 case OP_ASSSIGNDIV:
3575 case OP_ASSSIGNMOD:
3576 case OP_ASSSIGNADD:
3577 case OP_ASSSIGNSUB:
3578 case OP_ASSSIGNAND:
3579 case OP_ASSSIGNNOT:
3580 case OP_ASSSIGNOR:
3581 case OP_ASSSIGNSHL:
3582 case OP_ASSSIGNSHR:
3583 return 1;
3584 default:
3585 return 0;
3589 /****************************************************************************
3590 * WCMD_pushnumber
3591 * Push either a number or name (environment variable) onto the supplied
3592 * stack
3594 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3595 VARSTACK *thisstack = heap_alloc(sizeof(VARSTACK));
3596 thisstack->isnum = (var == NULL);
3597 if (var) {
3598 thisstack->variable = var;
3599 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3600 } else {
3601 thisstack->value = num;
3602 WINE_TRACE("Pushed number %d\n", num);
3604 thisstack->next = *varstack;
3605 *varstack = thisstack;
3608 /****************************************************************************
3609 * WCMD_peeknumber
3610 * Returns the value of the top number or environment variable on the stack
3611 * and leaves the item on the stack.
3613 static int WCMD_peeknumber(VARSTACK **varstack) {
3614 int result = 0;
3615 VARSTACK *thisvar;
3617 if (varstack) {
3618 thisvar = *varstack;
3619 if (!thisvar->isnum) {
3620 WCHAR tmpstr[MAXSTRING];
3621 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3622 result = strtoulW(tmpstr,NULL,0);
3624 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3625 } else {
3626 result = thisvar->value;
3629 WINE_TRACE("Peeked number %d\n", result);
3630 return result;
3633 /****************************************************************************
3634 * WCMD_popnumber
3635 * Returns the value of the top number or environment variable on the stack
3636 * and removes the item from the stack.
3638 static int WCMD_popnumber(VARSTACK **varstack) {
3639 int result = 0;
3640 VARSTACK *thisvar;
3642 if (varstack) {
3643 thisvar = *varstack;
3644 result = WCMD_peeknumber(varstack);
3645 if (!thisvar->isnum) heap_free(thisvar->variable);
3646 *varstack = thisvar->next;
3647 heap_free(thisvar);
3649 WINE_TRACE("Popped number %d\n", result);
3650 return result;
3653 /****************************************************************************
3654 * WCMD_pushoperator
3655 * Push an operator onto the supplied stack
3657 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3658 OPSTACK *thisstack = heap_alloc(sizeof(OPSTACK));
3659 thisstack->precedence = precedence;
3660 thisstack->op = op;
3661 thisstack->next = *opstack;
3662 WINE_TRACE("Pushed operator %c\n", op);
3663 *opstack = thisstack;
3666 /****************************************************************************
3667 * WCMD_popoperator
3668 * Returns the operator from the top of the stack and removes the item from
3669 * the stack.
3671 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3672 WCHAR result = 0;
3673 OPSTACK *thisop;
3675 if (opstack) {
3676 thisop = *opstack;
3677 result = thisop->op;
3678 *opstack = thisop->next;
3679 heap_free(thisop);
3681 WINE_TRACE("Popped operator %c\n", result);
3682 return result;
3685 /****************************************************************************
3686 * WCMD_reduce
3687 * Actions the top operator on the stack against the first and sometimes
3688 * second value on the variable stack, and pushes the result
3689 * Returns non-zero on error.
3691 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3692 WCHAR thisop;
3693 int var1,var2;
3694 int rc = 0;
3696 if (!*opstack || !*varstack) {
3697 WINE_TRACE("No operators for the reduce\n");
3698 return WCMD_NOOPERATOR;
3701 /* Remove the top operator */
3702 thisop = WCMD_popoperator(opstack);
3703 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
3705 /* One variable operators */
3706 var1 = WCMD_popnumber(varstack);
3707 switch (thisop) {
3708 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3709 break;
3710 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3711 break;
3712 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3713 break;
3714 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3715 break;
3718 /* Two variable operators */
3719 if (!*varstack) {
3720 WINE_TRACE("No operands left for the reduce?\n");
3721 return WCMD_NOOPERAND;
3723 switch (thisop) {
3724 case '!':
3725 case '~':
3726 case OP_POSITIVE:
3727 case OP_NEGATIVE:
3728 break; /* Handled above */
3729 case '*': var2 = WCMD_popnumber(varstack);
3730 WCMD_pushnumber(NULL, var2*var1, varstack);
3731 break;
3732 case '/': var2 = WCMD_popnumber(varstack);
3733 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3734 WCMD_pushnumber(NULL, var2/var1, varstack);
3735 break;
3736 case '+': var2 = WCMD_popnumber(varstack);
3737 WCMD_pushnumber(NULL, var2+var1, varstack);
3738 break;
3739 case '-': var2 = WCMD_popnumber(varstack);
3740 WCMD_pushnumber(NULL, var2-var1, varstack);
3741 break;
3742 case '&': var2 = WCMD_popnumber(varstack);
3743 WCMD_pushnumber(NULL, var2&var1, varstack);
3744 break;
3745 case '%': var2 = WCMD_popnumber(varstack);
3746 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3747 WCMD_pushnumber(NULL, var2%var1, varstack);
3748 break;
3749 case '^': var2 = WCMD_popnumber(varstack);
3750 WCMD_pushnumber(NULL, var2^var1, varstack);
3751 break;
3752 case '<': var2 = WCMD_popnumber(varstack);
3753 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3754 which differs from the compiler (for example gcc) so being explicit. */
3755 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3756 WCMD_pushnumber(NULL, 0, varstack);
3757 } else {
3758 WCMD_pushnumber(NULL, var2<<var1, varstack);
3760 break;
3761 case '>': var2 = WCMD_popnumber(varstack);
3762 WCMD_pushnumber(NULL, var2>>var1, varstack);
3763 break;
3764 case '|': var2 = WCMD_popnumber(varstack);
3765 WCMD_pushnumber(NULL, var2|var1, varstack);
3766 break;
3768 case OP_ASSSIGNMUL:
3769 case OP_ASSSIGNDIV:
3770 case OP_ASSSIGNMOD:
3771 case OP_ASSSIGNADD:
3772 case OP_ASSSIGNSUB:
3773 case OP_ASSSIGNAND:
3774 case OP_ASSSIGNNOT:
3775 case OP_ASSSIGNOR:
3776 case OP_ASSSIGNSHL:
3777 case OP_ASSSIGNSHR:
3779 int i = 0;
3781 /* The left of an equals must be one variable */
3782 if (!(*varstack) || (*varstack)->isnum) {
3783 return WCMD_NOOPERAND;
3786 /* Make the number stack grow by inserting the value of the variable */
3787 var2 = WCMD_peeknumber(varstack);
3788 WCMD_pushnumber(NULL, var2, varstack);
3789 WCMD_pushnumber(NULL, var1, varstack);
3791 /* Make the operand stack grow by pushing the assign operator plus the
3792 operator to perform */
3793 while (calcassignments[i].op != ' ' &&
3794 calcassignments[i].calculatedop != thisop) {
3795 i++;
3797 if (calcassignments[i].calculatedop == ' ') {
3798 WINE_ERR("Unexpected operator %c\n", thisop);
3799 return WCMD_NOOPERATOR;
3801 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3802 WCMD_pushoperator(calcassignments[i].op,
3803 WCMD_getprecedence(calcassignments[i].op), opstack);
3804 break;
3807 case '=':
3809 WCHAR intFormat[] = {'%','d','\0'};
3810 WCHAR result[MAXSTRING];
3812 /* Build the result, then push it onto the stack */
3813 sprintfW(result, intFormat, var1);
3814 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3815 wine_dbgstr_w(result));
3816 SetEnvironmentVariableW((*varstack)->variable, result);
3817 var2 = WCMD_popnumber(varstack);
3818 WCMD_pushnumber(NULL, var1, varstack);
3819 break;
3822 default: WINE_ERR("Unrecognized operator %c\n", thisop);
3825 return rc;
3829 /****************************************************************************
3830 * WCMD_handleExpression
3831 * Handles an expression provided to set /a - If it finds brackets, it uses
3832 * recursion to process the parts in brackets.
3834 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3836 static const WCHAR mathDelims[] = {' ','\t','(',')','!','~','-','*','/','%',
3837 '+','<','>','&','^','|','=',',','\0' };
3838 int rc = 0;
3839 WCHAR *pos;
3840 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3841 OPSTACK *opstackhead = NULL;
3842 VARSTACK *varstackhead = NULL;
3843 WCHAR foundhalf = 0;
3845 /* Initialize */
3846 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3847 pos = *expr;
3849 /* Iterate through until whole expression is processed */
3850 while (pos && *pos) {
3851 BOOL treatasnumber;
3853 /* Skip whitespace to get to the next character to process*/
3854 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3855 if (!*pos) goto exprreturn;
3857 /* If we have found anything other than an operator then it's a number/variable */
3858 if (strchrW(mathDelims, *pos) == NULL) {
3859 WCHAR *parmstart, *parm, *dupparm;
3860 WCHAR *nextpos;
3862 /* Cannot have an expression with var/number twice, without an operator
3863 in-between, nor or number following a half constructed << or >> operator */
3864 if (lastwasnumber || foundhalf) {
3865 rc = WCMD_NOOPERATOR;
3866 goto exprerrorreturn;
3868 lastwasnumber = TRUE;
3870 if (isdigitW(*pos)) {
3871 /* For a number - just push it onto the stack */
3872 int num = strtoulW(pos, &nextpos, 0);
3873 WCMD_pushnumber(NULL, num, &varstackhead);
3874 pos = nextpos;
3876 /* Verify the number was validly formed */
3877 if (*nextpos && (strchrW(mathDelims, *nextpos) == NULL)) {
3878 rc = WCMD_BADHEXOCT;
3879 goto exprerrorreturn;
3881 } else {
3883 /* For a variable - just push it onto the stack */
3884 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3885 dupparm = heap_strdupW(parm);
3886 WCMD_pushnumber(dupparm, 0, &varstackhead);
3887 pos = parmstart + strlenW(dupparm);
3889 continue;
3892 /* We have found an operator. Some operators are one character, some two, and the minus
3893 and plus signs need special processing as they can be either operators or just influence
3894 the parameter which follows them */
3895 if (foundhalf && (*pos != foundhalf)) {
3896 /* Badly constructed operator pair */
3897 rc = WCMD_NOOPERATOR;
3898 goto exprerrorreturn;
3901 treatasnumber = FALSE; /* We are processing an operand */
3902 switch (*pos) {
3904 /* > and < are special as they are double character operators (and spaces can be between them!)
3905 If we see these for the first time, set a flag, and second time around we continue.
3906 Note these double character operators are stored as just one of the characters on the stack */
3907 case '>':
3908 case '<': if (!foundhalf) {
3909 foundhalf = *pos;
3910 pos++;
3911 break;
3913 /* We have found the rest, so clear up the knowledge of the half completed part and
3914 drop through to normal operator processing */
3915 foundhalf = 0;
3916 /* drop through */
3918 case '=': if (*pos=='=') {
3919 /* = is special cased as if the last was an operator then we may have e.g. += or
3920 *= etc which we need to handle by replacing the operator that is on the stack
3921 with a calculated assignment equivalent */
3922 if (!lastwasnumber && opstackhead) {
3923 int i = 0;
3924 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3925 i++;
3927 if (calcassignments[i].op == ' ') {
3928 rc = WCMD_NOOPERAND;
3929 goto exprerrorreturn;
3930 } else {
3931 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3932 when the general operator handling happens further down. */
3933 *pos = calcassignments[i].calculatedop;
3934 WCMD_popoperator(&opstackhead);
3938 /* Drop though */
3940 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3941 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3942 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
3943 /* drop through */
3944 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
3945 /* drop through */
3947 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3948 case '!': /* drop through */
3949 case '~': /* drop through */
3950 case '/': /* drop through */
3951 case '%': /* drop through */
3952 case '&': /* drop through */
3953 case '^': /* drop through */
3954 case '*': /* drop through */
3955 case '|':
3956 /* General code for handling most of the operators - look at the
3957 precedence of the top item on the stack, and see if we need to
3958 action the stack before we push something else onto it. */
3960 int precedence = WCMD_getprecedence(*pos);
3961 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
3962 precedence, !opstackhead?-1:opstackhead->precedence);
3964 /* In general, for things with the same precedence, reduce immediately
3965 except for assignments and unary operators which do not */
3966 while (!rc && opstackhead &&
3967 ((opstackhead->precedence > precedence) ||
3968 ((opstackhead->precedence == precedence) &&
3969 (precedence != 1) && (precedence != 8)))) {
3970 rc = WCMD_reduce(&opstackhead, &varstackhead);
3972 if (rc) goto exprerrorreturn;
3973 WCMD_pushoperator(*pos, precedence, &opstackhead);
3974 pos++;
3975 break;
3978 /* comma means start a new expression, ie calculate what we have */
3979 case ',':
3981 int prevresult = -1;
3982 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
3983 while (!rc && opstackhead) {
3984 rc = WCMD_reduce(&opstackhead, &varstackhead);
3986 if (rc) goto exprerrorreturn;
3987 /* If we have anything other than one number left, error
3988 otherwise throw the number away */
3989 if (!varstackhead || varstackhead->next) {
3990 rc = WCMD_NOOPERATOR;
3991 goto exprerrorreturn;
3993 prevresult = WCMD_popnumber(&varstackhead);
3994 WINE_TRACE("Expression resolved to %d\n", prevresult);
3995 heap_free(varstackhead);
3996 varstackhead = NULL;
3997 pos++;
3998 break;
4001 /* Open bracket - use iteration to parse the inner expression, then continue */
4002 case '(' : {
4003 int exprresult = 0;
4004 pos++;
4005 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
4006 if (rc) goto exprerrorreturn;
4007 WCMD_pushnumber(NULL, exprresult, &varstackhead);
4008 break;
4011 /* Close bracket - we have finished this depth, calculate and return */
4012 case ')' : {
4013 pos++;
4014 treatasnumber = TRUE; /* Things in brackets result in a number */
4015 if (depth == 0) {
4016 rc = WCMD_BADPAREN;
4017 goto exprerrorreturn;
4019 goto exprreturn;
4022 default:
4023 WINE_ERR("Unrecognized operator %c\n", *pos);
4024 pos++;
4026 lastwasnumber = treatasnumber;
4029 exprreturn:
4030 *expr = pos;
4032 /* We need to reduce until we have a single number (or variable) on the
4033 stack and set the return value to that */
4034 while (!rc && opstackhead) {
4035 rc = WCMD_reduce(&opstackhead, &varstackhead);
4037 if (rc) goto exprerrorreturn;
4039 /* If we have anything other than one number left, error
4040 otherwise throw the number away */
4041 if (!varstackhead || varstackhead->next) {
4042 rc = WCMD_NOOPERATOR;
4043 goto exprerrorreturn;
4046 /* Now get the number (and convert if it's just a variable name) */
4047 *ret = WCMD_popnumber(&varstackhead);
4049 exprerrorreturn:
4050 /* Free all remaining memory */
4051 while (opstackhead) WCMD_popoperator(&opstackhead);
4052 while (varstackhead) WCMD_popnumber(&varstackhead);
4054 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
4055 return rc;
4058 /****************************************************************************
4059 * WCMD_setshow_env
4061 * Set/Show the environment variables
4064 void WCMD_setshow_env (WCHAR *s) {
4066 LPVOID env;
4067 WCHAR *p;
4068 BOOL status;
4069 static const WCHAR parmP[] = {'/','P','\0'};
4070 static const WCHAR parmA[] = {'/','A','\0'};
4071 WCHAR string[MAXSTRING];
4073 if (param1[0] == 0x00 && quals[0] == 0x00) {
4074 env = GetEnvironmentStringsW();
4075 WCMD_setshow_sortenv( env, NULL );
4076 return;
4079 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4080 if (CompareStringW(LOCALE_USER_DEFAULT,
4081 NORM_IGNORECASE | SORT_STRINGSORT,
4082 s, 2, parmP, -1) == CSTR_EQUAL) {
4083 DWORD count;
4085 s += 2;
4086 while (*s && (*s==' ' || *s=='\t')) s++;
4087 /* set /P "var=value"jim ignores anything after the last quote */
4088 if (*s=='\"') {
4089 WCHAR *lastquote;
4090 lastquote = WCMD_strip_quotes(s);
4091 if (lastquote) *lastquote = 0x00;
4092 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4095 /* If no parameter, or no '=' sign, return an error */
4096 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
4097 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4098 return;
4101 /* Output the prompt */
4102 *p++ = '\0';
4103 if (strlenW(p) != 0) WCMD_output_asis(p);
4105 /* Read the reply */
4106 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4107 if (count > 1) {
4108 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4109 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4110 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4111 wine_dbgstr_w(string));
4112 SetEnvironmentVariableW(s, string);
4115 /* See if /A supplied, and if so calculate the results of all the expressions */
4116 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4117 NORM_IGNORECASE | SORT_STRINGSORT,
4118 s, 2, parmA, -1) == CSTR_EQUAL) {
4119 /* /A supplied, so evaluate expressions and set variables appropriately */
4120 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4121 /* of the final computation */
4122 int result = 0;
4123 int rc = 0;
4124 WCHAR *thisexpr;
4125 WCHAR *src,*dst;
4127 /* Remove all quotes before doing any calculations */
4128 thisexpr = heap_alloc((strlenW(s+2)+1) * sizeof(WCHAR));
4129 src = s+2;
4130 dst = thisexpr;
4131 while (*src) {
4132 if (*src != '"') *dst++ = *src;
4133 src++;
4135 *dst = 0;
4137 /* Now calculate the results of the expression */
4138 src = thisexpr;
4139 rc = WCMD_handleExpression(&src, &result, 0);
4140 heap_free(thisexpr);
4142 /* If parsing failed, issue the error message */
4143 if (rc > 0) {
4144 WCMD_output_stderr(WCMD_LoadMessage(rc));
4145 return;
4148 /* If we have no context (interactive or cmd.exe /c) print the final result */
4149 if (!context) {
4150 static const WCHAR fmt[] = {'%','d','\0'};
4151 sprintfW(string, fmt, result);
4152 WCMD_output_asis(string);
4155 } else {
4156 DWORD gle;
4158 /* set "var=value"jim ignores anything after the last quote */
4159 if (*s=='\"') {
4160 WCHAR *lastquote;
4161 lastquote = WCMD_strip_quotes(s);
4162 if (lastquote) *lastquote = 0x00;
4163 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4166 p = strchrW (s, '=');
4167 if (p == NULL) {
4168 env = GetEnvironmentStringsW();
4169 if (WCMD_setshow_sortenv( env, s ) == 0) {
4170 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4171 errorlevel = 1;
4173 return;
4175 *p++ = '\0';
4177 if (strlenW(p) == 0) p = NULL;
4178 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4179 wine_dbgstr_w(p));
4180 status = SetEnvironmentVariableW(s, p);
4181 gle = GetLastError();
4182 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4183 errorlevel = 1;
4184 } else if (!status) WCMD_print_error();
4185 else errorlevel = 0;
4189 /****************************************************************************
4190 * WCMD_setshow_path
4192 * Set/Show the path environment variable
4195 void WCMD_setshow_path (const WCHAR *args) {
4197 WCHAR string[1024];
4198 DWORD status;
4199 static const WCHAR pathW[] = {'P','A','T','H','\0'};
4200 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
4202 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
4203 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
4204 if (status != 0) {
4205 WCMD_output_asis ( pathEqW);
4206 WCMD_output_asis ( string);
4207 WCMD_output_asis ( newlineW);
4209 else {
4210 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4213 else {
4214 if (*args == '=') args++; /* Skip leading '=' */
4215 status = SetEnvironmentVariableW(pathW, args);
4216 if (!status) WCMD_print_error();
4220 /****************************************************************************
4221 * WCMD_setshow_prompt
4223 * Set or show the command prompt.
4226 void WCMD_setshow_prompt (void) {
4228 WCHAR *s;
4229 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
4231 if (strlenW(param1) == 0) {
4232 SetEnvironmentVariableW(promptW, NULL);
4234 else {
4235 s = param1;
4236 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4237 if (strlenW(s) == 0) {
4238 SetEnvironmentVariableW(promptW, NULL);
4240 else SetEnvironmentVariableW(promptW, s);
4244 /****************************************************************************
4245 * WCMD_setshow_time
4247 * Set/Show the system time
4248 * FIXME: Can't change time yet
4251 void WCMD_setshow_time (void) {
4253 WCHAR curtime[64], buffer[64];
4254 DWORD count;
4255 SYSTEMTIME st;
4256 static const WCHAR parmT[] = {'/','T','\0'};
4258 if (strlenW(param1) == 0) {
4259 GetLocalTime(&st);
4260 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
4261 curtime, sizeof(curtime)/sizeof(WCHAR))) {
4262 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4263 if (strstrW (quals, parmT) == NULL) {
4264 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4265 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4266 if (count > 2) {
4267 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4271 else WCMD_print_error ();
4273 else {
4274 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4278 /****************************************************************************
4279 * WCMD_shift
4281 * Shift batch parameters.
4282 * Optional /n says where to start shifting (n=0-8)
4285 void WCMD_shift (const WCHAR *args) {
4286 int start;
4288 if (context != NULL) {
4289 WCHAR *pos = strchrW(args, '/');
4290 int i;
4292 if (pos == NULL) {
4293 start = 0;
4294 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4295 start = (*(pos+1) - '0');
4296 } else {
4297 SetLastError(ERROR_INVALID_PARAMETER);
4298 WCMD_print_error();
4299 return;
4302 WINE_TRACE("Shifting variables, starting at %d\n", start);
4303 for (i=start;i<=8;i++) {
4304 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4306 context -> shift_count[9] = context -> shift_count[9] + 1;
4311 /****************************************************************************
4312 * WCMD_start
4314 void WCMD_start(WCHAR *args)
4316 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
4317 '\\','s','t','a','r','t','.','e','x','e',0};
4318 static const WCHAR startDelims[] = { ' ', '\t', '/', '\0' };
4319 static const WCHAR prefixQuote[] = {'"','\\','"','\0'};
4320 static const WCHAR postfixQuote[] = {'\\','"','"','\0'};
4321 int argno;
4322 int have_title;
4323 WCHAR file[MAX_PATH];
4324 WCHAR *cmdline;
4325 STARTUPINFOW st;
4326 PROCESS_INFORMATION pi;
4328 GetWindowsDirectoryW( file, MAX_PATH );
4329 strcatW( file, exeW );
4330 cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 8) * sizeof(WCHAR) );
4331 strcpyW( cmdline, file );
4332 strcatW( cmdline, spaceW );
4334 /* The start built-in has some special command-line parsing properties
4335 * which will be outlined here.
4337 * both '\t' and ' ' are argument separators
4338 * '/' has a special double role as both separator and switch prefix, e.g.
4340 * > start /low/i
4341 * or
4342 * > start "title"/i
4344 * are valid ways to pass multiple options to start. In the latter case
4345 * '/i' is not a part of the title but parsed as a switch.
4347 * However, '=', ';' and ',' are not separators:
4348 * > start "deus"=ex,machina
4350 * will in fact open a console titled 'deus=ex,machina'
4352 * The title argument parsing code is only interested in quotes themselves,
4353 * it does not respect escaping of any kind and all quotes are dropped
4354 * from the resulting title, therefore:
4356 * > start "\"" hello"/low
4358 * actually opens a console titled '\ hello' with low priorities.
4360 * To not break compatibility with wine programs relying on
4361 * wine's separate 'start.exe', this program's peculiar console
4362 * title parsing is actually implemented in 'cmd.exe' which is the
4363 * application native Windows programs will use to invoke 'start'.
4365 * WCMD_parameter_with_delims will take care of everything for us.
4367 have_title = FALSE;
4368 for (argno=0; ; argno++) {
4369 WCHAR *thisArg, *argN;
4371 argN = NULL;
4372 thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, startDelims);
4374 /* No more parameters */
4375 if (!argN)
4376 break;
4378 /* Found the title */
4379 if (argN[0] == '"') {
4380 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
4381 have_title = TRUE;
4383 /* Copy all of the cmdline processed */
4384 memcpy(cmdline, args, sizeof(WCHAR) * (argN - args));
4385 cmdline[argN - args] = '\0';
4387 /* Add quoted title */
4388 strcatW(cmdline, prefixQuote);
4389 strcatW(cmdline, thisArg);
4390 strcatW(cmdline, postfixQuote);
4392 /* Concatenate remaining command-line */
4393 thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, startDelims);
4394 strcatW(cmdline, argN + strlenW(thisArg));
4396 break;
4399 /* Skipping a regular argument? */
4400 else if (argN != args && argN[-1] == '/') {
4401 continue;
4403 /* Not an argument nor the title, start of program arguments,
4404 * stop looking for title.
4406 } else
4407 break;
4410 /* build command-line if not built yet */
4411 if (!have_title) {
4412 strcatW( cmdline, args );
4415 memset( &st, 0, sizeof(STARTUPINFOW) );
4416 st.cb = sizeof(STARTUPINFOW);
4418 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4420 WaitForSingleObject( pi.hProcess, INFINITE );
4421 GetExitCodeProcess( pi.hProcess, &errorlevel );
4422 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4423 CloseHandle(pi.hProcess);
4424 CloseHandle(pi.hThread);
4426 else
4428 SetLastError(ERROR_FILE_NOT_FOUND);
4429 WCMD_print_error ();
4430 errorlevel = 9009;
4432 heap_free(cmdline);
4435 /****************************************************************************
4436 * WCMD_title
4438 * Set the console title
4440 void WCMD_title (const WCHAR *args) {
4441 SetConsoleTitleW(args);
4444 /****************************************************************************
4445 * WCMD_type
4447 * Copy a file to standard output.
4450 void WCMD_type (WCHAR *args) {
4452 int argno = 0;
4453 WCHAR *argN = args;
4454 BOOL writeHeaders = FALSE;
4456 if (param1[0] == 0x00) {
4457 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4458 return;
4461 if (param2[0] != 0x00) writeHeaders = TRUE;
4463 /* Loop through all args */
4464 errorlevel = 0;
4465 while (argN) {
4466 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4468 HANDLE h;
4469 WCHAR buffer[512];
4470 DWORD count;
4472 if (!argN) break;
4474 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4475 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4476 FILE_ATTRIBUTE_NORMAL, NULL);
4477 if (h == INVALID_HANDLE_VALUE) {
4478 WCMD_print_error ();
4479 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4480 errorlevel = 1;
4481 } else {
4482 if (writeHeaders) {
4483 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
4484 WCMD_output(fmt, thisArg);
4486 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
4487 if (count == 0) break; /* ReadFile reports success on EOF! */
4488 buffer[count] = 0;
4489 WCMD_output_asis (buffer);
4491 CloseHandle (h);
4496 /****************************************************************************
4497 * WCMD_more
4499 * Output either a file or stdin to screen in pages
4502 void WCMD_more (WCHAR *args) {
4504 int argno = 0;
4505 WCHAR *argN = args;
4506 WCHAR moreStr[100];
4507 WCHAR moreStrPage[100];
4508 WCHAR buffer[512];
4509 DWORD count;
4510 static const WCHAR moreStart[] = {'-','-',' ','\0'};
4511 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
4512 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
4513 ')',' ','-','-','\n','\0'};
4514 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
4516 /* Prefix the NLS more with '-- ', then load the text */
4517 errorlevel = 0;
4518 strcpyW(moreStr, moreStart);
4519 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
4520 (sizeof(moreStr)/sizeof(WCHAR))-3);
4522 if (param1[0] == 0x00) {
4524 /* Wine implements pipes via temporary files, and hence stdin is
4525 effectively reading from the file. This means the prompts for
4526 more are satisfied by the next line from the input (file). To
4527 avoid this, ensure stdin is to the console */
4528 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4529 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
4530 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4531 FILE_ATTRIBUTE_NORMAL, 0);
4532 WINE_TRACE("No parms - working probably in pipe mode\n");
4533 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4535 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4536 once you get in this bit unless due to a pipe, it's going to end badly... */
4537 wsprintfW(moreStrPage, moreFmt, moreStr);
4539 WCMD_enter_paged_mode(moreStrPage);
4540 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4541 if (count == 0) break; /* ReadFile reports success on EOF! */
4542 buffer[count] = 0;
4543 WCMD_output_asis (buffer);
4545 WCMD_leave_paged_mode();
4547 /* Restore stdin to what it was */
4548 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4549 CloseHandle(hConIn);
4551 return;
4552 } else {
4553 BOOL needsPause = FALSE;
4555 /* Loop through all args */
4556 WINE_TRACE("Parms supplied - working through each file\n");
4557 WCMD_enter_paged_mode(moreStrPage);
4559 while (argN) {
4560 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4561 HANDLE h;
4563 if (!argN) break;
4565 if (needsPause) {
4567 /* Wait */
4568 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
4569 WCMD_leave_paged_mode();
4570 WCMD_output_asis(moreStrPage);
4571 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
4572 WCMD_enter_paged_mode(moreStrPage);
4576 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4577 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4578 FILE_ATTRIBUTE_NORMAL, NULL);
4579 if (h == INVALID_HANDLE_VALUE) {
4580 WCMD_print_error ();
4581 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4582 errorlevel = 1;
4583 } else {
4584 ULONG64 curPos = 0;
4585 ULONG64 fileLen = 0;
4586 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4588 /* Get the file size */
4589 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4590 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4592 needsPause = TRUE;
4593 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
4594 if (count == 0) break; /* ReadFile reports success on EOF! */
4595 buffer[count] = 0;
4596 curPos += count;
4598 /* Update % count (would be used in WCMD_output_asis as prompt) */
4599 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
4601 WCMD_output_asis (buffer);
4603 CloseHandle (h);
4607 WCMD_leave_paged_mode();
4611 /****************************************************************************
4612 * WCMD_verify
4614 * Display verify flag.
4615 * FIXME: We don't actually do anything with the verify flag other than toggle
4616 * it...
4619 void WCMD_verify (const WCHAR *args) {
4621 int count;
4623 count = strlenW(args);
4624 if (count == 0) {
4625 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
4626 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
4627 return;
4629 if (lstrcmpiW(args, onW) == 0) {
4630 verify_mode = TRUE;
4631 return;
4633 else if (lstrcmpiW(args, offW) == 0) {
4634 verify_mode = FALSE;
4635 return;
4637 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4640 /****************************************************************************
4641 * WCMD_version
4643 * Display version info.
4646 void WCMD_version (void) {
4648 WCMD_output_asis (version_string);
4652 /****************************************************************************
4653 * WCMD_volume
4655 * Display volume information (set_label = FALSE)
4656 * Additionally set volume label (set_label = TRUE)
4657 * Returns 1 on success, 0 otherwise
4660 int WCMD_volume(BOOL set_label, const WCHAR *path)
4662 DWORD count, serial;
4663 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4664 BOOL status;
4666 if (strlenW(path) == 0) {
4667 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
4668 if (!status) {
4669 WCMD_print_error ();
4670 return 0;
4672 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
4673 &serial, NULL, NULL, NULL, 0);
4675 else {
4676 static const WCHAR fmt[] = {'%','s','\\','\0'};
4677 if ((path[1] != ':') || (strlenW(path) != 2)) {
4678 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4679 return 0;
4681 wsprintfW (curdir, fmt, path);
4682 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
4683 &serial, NULL,
4684 NULL, NULL, 0);
4686 if (!status) {
4687 WCMD_print_error ();
4688 return 0;
4690 if (label[0] != '\0') {
4691 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4692 curdir[0], label);
4694 else {
4695 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4696 curdir[0]);
4698 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4699 HIWORD(serial), LOWORD(serial));
4700 if (set_label) {
4701 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4702 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
4703 if (count > 1) {
4704 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4705 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4707 if (strlenW(path) != 0) {
4708 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4710 else {
4711 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4714 return 1;
4717 /**************************************************************************
4718 * WCMD_exit
4720 * Exit either the process, or just this batch program
4724 void WCMD_exit (CMD_LIST **cmdList) {
4726 static const WCHAR parmB[] = {'/','B','\0'};
4727 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
4729 if (context && lstrcmpiW(quals, parmB) == 0) {
4730 errorlevel = rc;
4731 context -> skip_rest = TRUE;
4732 *cmdList = NULL;
4733 } else {
4734 ExitProcess(rc);
4739 /*****************************************************************************
4740 * WCMD_assoc
4742 * Lists or sets file associations (assoc = TRUE)
4743 * Lists or sets file types (assoc = FALSE)
4745 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4747 HKEY key;
4748 DWORD accessOptions = KEY_READ;
4749 WCHAR *newValue;
4750 LONG rc = ERROR_SUCCESS;
4751 WCHAR keyValue[MAXSTRING];
4752 DWORD valueLen = MAXSTRING;
4753 HKEY readKey;
4754 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
4755 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4757 /* See if parameter includes '=' */
4758 errorlevel = 0;
4759 newValue = strchrW(args, '=');
4760 if (newValue) accessOptions |= KEY_WRITE;
4762 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4763 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
4764 accessOptions, &key) != ERROR_SUCCESS) {
4765 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4766 return;
4769 /* If no parameters then list all associations */
4770 if (*args == 0x00) {
4771 int index = 0;
4773 /* Enumerate all the keys */
4774 while (rc != ERROR_NO_MORE_ITEMS) {
4775 WCHAR keyName[MAXSTRING];
4776 DWORD nameLen;
4778 /* Find the next value */
4779 nameLen = MAXSTRING;
4780 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4782 if (rc == ERROR_SUCCESS) {
4784 /* Only interested in extension ones if assoc, or others
4785 if not assoc */
4786 if ((keyName[0] == '.' && assoc) ||
4787 (!(keyName[0] == '.') && (!assoc)))
4789 WCHAR subkey[MAXSTRING];
4790 strcpyW(subkey, keyName);
4791 if (!assoc) strcatW(subkey, shOpCmdW);
4793 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4795 valueLen = sizeof(keyValue)/sizeof(WCHAR);
4796 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4797 WCMD_output_asis(keyName);
4798 WCMD_output_asis(equalW);
4799 /* If no default value found, leave line empty after '=' */
4800 if (rc == ERROR_SUCCESS) {
4801 WCMD_output_asis(keyValue);
4803 WCMD_output_asis(newlineW);
4804 RegCloseKey(readKey);
4810 } else {
4812 /* Parameter supplied - if no '=' on command line, it's a query */
4813 if (newValue == NULL) {
4814 WCHAR *space;
4815 WCHAR subkey[MAXSTRING];
4817 /* Query terminates the parameter at the first space */
4818 strcpyW(keyValue, args);
4819 space = strchrW(keyValue, ' ');
4820 if (space) *space=0x00;
4822 /* Set up key name */
4823 strcpyW(subkey, keyValue);
4824 if (!assoc) strcatW(subkey, shOpCmdW);
4826 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4828 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4829 WCMD_output_asis(args);
4830 WCMD_output_asis(equalW);
4831 /* If no default value found, leave line empty after '=' */
4832 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4833 WCMD_output_asis(newlineW);
4834 RegCloseKey(readKey);
4836 } else {
4837 WCHAR msgbuffer[MAXSTRING];
4839 /* Load the translated 'File association not found' */
4840 if (assoc) {
4841 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4842 } else {
4843 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
4845 WCMD_output_stderr(msgbuffer, keyValue);
4846 errorlevel = 2;
4849 /* Not a query - it's a set or clear of a value */
4850 } else {
4852 WCHAR subkey[MAXSTRING];
4854 /* Get pointer to new value */
4855 *newValue = 0x00;
4856 newValue++;
4858 /* Set up key name */
4859 strcpyW(subkey, args);
4860 if (!assoc) strcatW(subkey, shOpCmdW);
4862 /* If nothing after '=' then clear value - only valid for ASSOC */
4863 if (*newValue == 0x00) {
4865 if (assoc) rc = RegDeleteKeyW(key, args);
4866 if (assoc && rc == ERROR_SUCCESS) {
4867 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4869 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4870 WCMD_print_error();
4871 errorlevel = 2;
4873 } else {
4874 WCHAR msgbuffer[MAXSTRING];
4876 /* Load the translated 'File association not found' */
4877 if (assoc) {
4878 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
4879 sizeof(msgbuffer)/sizeof(WCHAR));
4880 } else {
4881 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
4882 sizeof(msgbuffer)/sizeof(WCHAR));
4884 WCMD_output_stderr(msgbuffer, keyValue);
4885 errorlevel = 2;
4888 /* It really is a set value = contents */
4889 } else {
4890 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4891 accessOptions, NULL, &readKey, NULL);
4892 if (rc == ERROR_SUCCESS) {
4893 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4894 (LPBYTE)newValue,
4895 sizeof(WCHAR) * (strlenW(newValue) + 1));
4896 RegCloseKey(readKey);
4899 if (rc != ERROR_SUCCESS) {
4900 WCMD_print_error();
4901 errorlevel = 2;
4902 } else {
4903 WCMD_output_asis(args);
4904 WCMD_output_asis(equalW);
4905 WCMD_output_asis(newValue);
4906 WCMD_output_asis(newlineW);
4912 /* Clean up */
4913 RegCloseKey(key);
4916 /****************************************************************************
4917 * WCMD_color
4919 * Colors the terminal screen.
4922 void WCMD_color (void) {
4924 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4925 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4927 if (param1[0] != 0x00 && strlenW(param1) > 2) {
4928 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4929 return;
4932 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4934 COORD topLeft;
4935 DWORD screenSize;
4936 DWORD color = 0;
4938 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4940 topLeft.X = 0;
4941 topLeft.Y = 0;
4943 /* Convert the color hex digits */
4944 if (param1[0] == 0x00) {
4945 color = defaultColor;
4946 } else {
4947 color = strtoulW(param1, NULL, 16);
4950 /* Fail if fg == bg color */
4951 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4952 errorlevel = 1;
4953 return;
4956 /* Set the current screen contents and ensure all future writes
4957 remain this color */
4958 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4959 SetConsoleTextAttribute(hStdOut, color);