ntdll: Align string data in RtlCreateProcessParametersEx().
[wine.git] / programs / cmd / builtins.c
blob25b98813ba4483694be6dd578d9b47f0deb67cc2
1 /*
2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * FIXME:
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
31 #include "wcmd.h"
32 #include <shellapi.h>
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
37 extern int defaultColor;
38 extern BOOL echo_mode;
39 extern BOOL interactive;
41 struct env_stack *pushd_directories;
42 const WCHAR dotW[] = {'.','\0'};
43 const WCHAR dotdotW[] = {'.','.','\0'};
44 const WCHAR nullW[] = {'\0'};
45 const WCHAR starW[] = {'*','\0'};
46 const WCHAR slashW[] = {'\\','\0'};
47 const WCHAR equalW[] = {'=','\0'};
48 const WCHAR wildcardsW[] = {'*','?','\0'};
49 const WCHAR slashstarW[] = {'\\','*','\0'};
50 const WCHAR deviceW[] = {'\\','\\','.','\\','\0'};
51 const WCHAR inbuilt[][10] = {
52 {'C','A','L','L','\0'},
53 {'C','D','\0'},
54 {'C','H','D','I','R','\0'},
55 {'C','L','S','\0'},
56 {'C','O','P','Y','\0'},
57 {'C','T','T','Y','\0'},
58 {'D','A','T','E','\0'},
59 {'D','E','L','\0'},
60 {'D','I','R','\0'},
61 {'E','C','H','O','\0'},
62 {'E','R','A','S','E','\0'},
63 {'F','O','R','\0'},
64 {'G','O','T','O','\0'},
65 {'H','E','L','P','\0'},
66 {'I','F','\0'},
67 {'L','A','B','E','L','\0'},
68 {'M','D','\0'},
69 {'M','K','D','I','R','\0'},
70 {'M','O','V','E','\0'},
71 {'P','A','T','H','\0'},
72 {'P','A','U','S','E','\0'},
73 {'P','R','O','M','P','T','\0'},
74 {'R','E','M','\0'},
75 {'R','E','N','\0'},
76 {'R','E','N','A','M','E','\0'},
77 {'R','D','\0'},
78 {'R','M','D','I','R','\0'},
79 {'S','E','T','\0'},
80 {'S','H','I','F','T','\0'},
81 {'S','T','A','R','T','\0'},
82 {'T','I','M','E','\0'},
83 {'T','I','T','L','E','\0'},
84 {'T','Y','P','E','\0'},
85 {'V','E','R','I','F','Y','\0'},
86 {'V','E','R','\0'},
87 {'V','O','L','\0'},
88 {'E','N','D','L','O','C','A','L','\0'},
89 {'S','E','T','L','O','C','A','L','\0'},
90 {'P','U','S','H','D','\0'},
91 {'P','O','P','D','\0'},
92 {'A','S','S','O','C','\0'},
93 {'C','O','L','O','R','\0'},
94 {'F','T','Y','P','E','\0'},
95 {'M','O','R','E','\0'},
96 {'C','H','O','I','C','E','\0'},
97 {'M','K','L','I','N','K','\0'},
98 {'E','X','I','T','\0'}
100 static const WCHAR externals[][10] = {
101 {'A','T','T','R','I','B','\0'},
102 {'X','C','O','P','Y','\0'}
104 static const WCHAR onW[] = {'O','N','\0'};
105 static const WCHAR offW[] = {'O','F','F','\0'};
106 static const WCHAR parmY[] = {'/','Y','\0'};
107 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
108 static const WCHAR eqeqW[] = {'=','=','\0'};
110 static HINSTANCE hinst;
111 struct env_stack *saved_environment;
112 static BOOL verify_mode = FALSE;
114 /* set /a routines work from single character operators, but some of the
115 operators are multiple character ones, especially the assignment ones.
116 Temporarily represent these using the values below on the operator stack */
117 #define OP_POSITIVE 'P'
118 #define OP_NEGATIVE 'N'
119 #define OP_ASSSIGNMUL 'a'
120 #define OP_ASSSIGNDIV 'b'
121 #define OP_ASSSIGNMOD 'c'
122 #define OP_ASSSIGNADD 'd'
123 #define OP_ASSSIGNSUB 'e'
124 #define OP_ASSSIGNAND 'f'
125 #define OP_ASSSIGNNOT 'g'
126 #define OP_ASSSIGNOR 'h'
127 #define OP_ASSSIGNSHL 'i'
128 #define OP_ASSSIGNSHR 'j'
130 /* This maintains a stack of operators, holding both the operator precedence
131 and the single character representation of the operator in question */
132 typedef struct _OPSTACK
134 int precedence;
135 WCHAR op;
136 struct _OPSTACK *next;
137 } OPSTACK;
139 /* This maintains a stack of values, where each value can either be a
140 numeric value, or a string representing an environment variable */
141 typedef struct _VARSTACK
143 BOOL isnum;
144 WCHAR *variable;
145 int value;
146 struct _VARSTACK *next;
147 } VARSTACK;
149 /* This maintains a mapping between the calculated operator and the
150 single character representation for the assignment operators. */
151 static struct
153 WCHAR op;
154 WCHAR calculatedop;
155 } calcassignments[] =
157 {'*', OP_ASSSIGNMUL},
158 {'/', OP_ASSSIGNDIV},
159 {'%', OP_ASSSIGNMOD},
160 {'+', OP_ASSSIGNADD},
161 {'-', OP_ASSSIGNSUB},
162 {'&', OP_ASSSIGNAND},
163 {'^', OP_ASSSIGNNOT},
164 {'|', OP_ASSSIGNOR},
165 {'<', OP_ASSSIGNSHL},
166 {'>', OP_ASSSIGNSHR},
167 {' ',' '}
170 /**************************************************************************
171 * WCMD_ask_confirm
173 * Issue a message and ask for confirmation, waiting on a valid answer.
175 * Returns True if Y (or A) answer is selected
176 * If optionAll contains a pointer, ALL is allowed, and if answered
177 * set to TRUE
180 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
181 BOOL *optionAll) {
183 UINT msgid;
184 WCHAR confirm[MAXSTRING];
185 WCHAR options[MAXSTRING];
186 WCHAR Ybuffer[MAXSTRING];
187 WCHAR Nbuffer[MAXSTRING];
188 WCHAR Abuffer[MAXSTRING];
189 WCHAR answer[MAX_PATH] = {'\0'};
190 DWORD count = 0;
192 /* Load the translated valid answers */
193 if (showSureText)
194 LoadStringW(hinst, WCMD_CONFIRM, confirm, ARRAY_SIZE(confirm));
195 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
196 LoadStringW(hinst, msgid, options, ARRAY_SIZE(options));
197 LoadStringW(hinst, WCMD_YES, Ybuffer, ARRAY_SIZE(Ybuffer));
198 LoadStringW(hinst, WCMD_NO, Nbuffer, ARRAY_SIZE(Nbuffer));
199 LoadStringW(hinst, WCMD_ALL, Abuffer, ARRAY_SIZE(Abuffer));
201 /* Loop waiting on a valid answer */
202 if (optionAll)
203 *optionAll = FALSE;
204 while (1)
206 WCMD_output_asis (message);
207 if (showSureText)
208 WCMD_output_asis (confirm);
209 WCMD_output_asis (options);
210 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, ARRAY_SIZE(answer), &count);
211 answer[0] = toupperW(answer[0]);
212 if (answer[0] == Ybuffer[0])
213 return TRUE;
214 if (answer[0] == Nbuffer[0])
215 return FALSE;
216 if (optionAll && answer[0] == Abuffer[0])
218 *optionAll = TRUE;
219 return TRUE;
224 /****************************************************************************
225 * WCMD_clear_screen
227 * Clear the terminal screen.
230 void WCMD_clear_screen (void) {
232 /* Emulate by filling the screen from the top left to bottom right with
233 spaces, then moving the cursor to the top left afterwards */
234 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
235 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
237 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
239 COORD topLeft;
240 DWORD screenSize, written;
242 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
244 topLeft.X = 0;
245 topLeft.Y = 0;
246 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
247 FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
248 SetConsoleCursorPosition(hStdOut, topLeft);
252 /****************************************************************************
253 * WCMD_change_tty
255 * Change the default i/o device (ie redirect STDin/STDout).
258 void WCMD_change_tty (void) {
260 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
264 /****************************************************************************
265 * WCMD_choice
269 void WCMD_choice (const WCHAR * args) {
271 static const WCHAR bellW[] = {7,0};
272 static const WCHAR commaW[] = {',',0};
273 static const WCHAR bracket_open[] = {'[',0};
274 static const WCHAR bracket_close[] = {']','?',0};
275 WCHAR answer[16];
276 WCHAR buffer[16];
277 WCHAR *ptr = NULL;
278 WCHAR *opt_c = NULL;
279 WCHAR *my_command = NULL;
280 WCHAR opt_default = 0;
281 DWORD opt_timeout = 0;
282 DWORD count;
283 DWORD oldmode;
284 BOOL have_console;
285 BOOL opt_n = FALSE;
286 BOOL opt_s = FALSE;
288 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
289 errorlevel = 0;
291 my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
293 ptr = WCMD_skip_leading_spaces(my_command);
294 while (*ptr == '/') {
295 switch (toupperW(ptr[1])) {
296 case 'C':
297 ptr += 2;
298 /* the colon is optional */
299 if (*ptr == ':')
300 ptr++;
302 if (!*ptr || isspaceW(*ptr)) {
303 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
304 heap_free(my_command);
305 return;
308 /* remember the allowed keys (overwrite previous /C option) */
309 opt_c = ptr;
310 while (*ptr && (!isspaceW(*ptr)))
311 ptr++;
313 if (*ptr) {
314 /* terminate allowed chars */
315 *ptr = 0;
316 ptr = WCMD_skip_leading_spaces(&ptr[1]);
318 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
319 break;
321 case 'N':
322 opt_n = TRUE;
323 ptr = WCMD_skip_leading_spaces(&ptr[2]);
324 break;
326 case 'S':
327 opt_s = TRUE;
328 ptr = WCMD_skip_leading_spaces(&ptr[2]);
329 break;
331 case 'T':
332 ptr = &ptr[2];
333 /* the colon is optional */
334 if (*ptr == ':')
335 ptr++;
337 opt_default = *ptr++;
339 if (!opt_default || (*ptr != ',')) {
340 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
341 heap_free(my_command);
342 return;
344 ptr++;
346 count = 0;
347 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
348 count++;
349 ptr++;
352 answer[count] = 0;
353 opt_timeout = atoiW(answer);
355 ptr = WCMD_skip_leading_spaces(ptr);
356 break;
358 default:
359 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
360 heap_free(my_command);
361 return;
365 if (opt_timeout)
366 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
368 if (have_console)
369 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
371 /* use default keys, when needed: localized versions of "Y"es and "No" */
372 if (!opt_c) {
373 LoadStringW(hinst, WCMD_YES, buffer, ARRAY_SIZE(buffer));
374 LoadStringW(hinst, WCMD_NO, buffer + 1, ARRAY_SIZE(buffer) - 1);
375 opt_c = buffer;
376 buffer[2] = 0;
379 /* print the question, when needed */
380 if (*ptr)
381 WCMD_output_asis(ptr);
383 if (!opt_s) {
384 struprW(opt_c);
385 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
388 if (!opt_n) {
389 /* print a list of all allowed answers inside brackets */
390 WCMD_output_asis(bracket_open);
391 ptr = opt_c;
392 answer[1] = 0;
393 while ((answer[0] = *ptr++)) {
394 WCMD_output_asis(answer);
395 if (*ptr)
396 WCMD_output_asis(commaW);
398 WCMD_output_asis(bracket_close);
401 while (TRUE) {
403 /* FIXME: Add support for option /T */
404 answer[1] = 0; /* terminate single character string */
405 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
407 if (!opt_s)
408 answer[0] = toupperW(answer[0]);
410 ptr = strchrW(opt_c, answer[0]);
411 if (ptr) {
412 WCMD_output_asis(answer);
413 WCMD_output_asis(newlineW);
414 if (have_console)
415 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
417 errorlevel = (ptr - opt_c) + 1;
418 WINE_TRACE("answer: %d\n", errorlevel);
419 heap_free(my_command);
420 return;
422 else
424 /* key not allowed: play the bell */
425 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
426 WCMD_output_asis(bellW);
431 /****************************************************************************
432 * WCMD_AppendEOF
434 * Adds an EOF onto the end of a file
435 * Returns TRUE on success
437 static BOOL WCMD_AppendEOF(WCHAR *filename)
439 HANDLE h;
440 DWORD bytes_written;
442 char eof = '\x1a';
444 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
445 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
446 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
448 if (h == INVALID_HANDLE_VALUE) {
449 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
450 return FALSE;
451 } else {
452 SetFilePointer (h, 0, NULL, FILE_END);
453 if (!WriteFile(h, &eof, 1, &bytes_written, NULL)) {
454 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
455 CloseHandle(h);
456 return FALSE;
458 CloseHandle(h);
460 return TRUE;
463 /****************************************************************************
464 * WCMD_IsSameFile
466 * Checks if the two paths reference to the same file
468 static BOOL WCMD_IsSameFile(const WCHAR *name1, const WCHAR *name2)
470 BOOL ret = FALSE;
471 HANDLE file1 = INVALID_HANDLE_VALUE, file2 = INVALID_HANDLE_VALUE;
472 BY_HANDLE_FILE_INFORMATION info1, info2;
474 file1 = CreateFileW(name1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
475 if (file1 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file1, &info1))
476 goto end;
478 file2 = CreateFileW(name2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
479 if (file2 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file2, &info2))
480 goto end;
482 ret = info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
483 && info1.nFileIndexHigh == info2.nFileIndexHigh
484 && info1.nFileIndexLow == info2.nFileIndexLow;
485 end:
486 if (file1 != INVALID_HANDLE_VALUE)
487 CloseHandle(file1);
488 if (file2 != INVALID_HANDLE_VALUE)
489 CloseHandle(file2);
490 return ret;
493 /****************************************************************************
494 * WCMD_ManualCopy
496 * Copies from a file
497 * optionally reading only until EOF (ascii copy)
498 * optionally appending onto an existing file (append)
499 * Returns TRUE on success
501 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
503 HANDLE in,out;
504 BOOL ok;
505 DWORD bytesread, byteswritten;
507 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
508 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
510 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
511 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
512 if (in == INVALID_HANDLE_VALUE) {
513 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
514 return FALSE;
517 /* Open the output file, overwriting if not appending */
518 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
519 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
520 if (out == INVALID_HANDLE_VALUE) {
521 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
522 CloseHandle(in);
523 return FALSE;
526 /* Move to end of destination if we are going to append to it */
527 if (append) {
528 SetFilePointer(out, 0, NULL, FILE_END);
531 /* Loop copying data from source to destination until EOF read */
534 char buffer[MAXSTRING];
536 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
537 if (ok) {
539 /* Stop at first EOF */
540 if (ascii) {
541 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
542 if (ptr) bytesread = (ptr - buffer);
545 if (bytesread) {
546 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
547 if (!ok || byteswritten != bytesread) {
548 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
549 wine_dbgstr_w(dstname), GetLastError());
552 } else {
553 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
554 wine_dbgstr_w(srcname), GetLastError());
556 } while (ok && bytesread > 0);
558 CloseHandle(out);
559 CloseHandle(in);
560 return ok;
563 /****************************************************************************
564 * WCMD_copy
566 * Copy a file or wildcarded set.
567 * For ascii/binary type copies, it gets complex:
568 * Syntax on command line is
569 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
570 * Where first /a or /b sets 'mode in operation' until another is found
571 * once another is found, it applies to the file preceding the /a or /b
572 * In addition each filename can contain wildcards
573 * To make matters worse, the + may be in the same parameter (i.e. no
574 * whitespace) or with whitespace separating it
576 * ASCII mode on read == read and stop at first EOF
577 * ASCII mode on write == append EOF to destination
578 * Binary == copy as-is
580 * Design of this is to build up a list of files which will be copied into a
581 * list, then work through the list file by file.
582 * If no destination is specified, it defaults to the name of the first file in
583 * the list, but the current directory.
587 void WCMD_copy(WCHAR * args) {
589 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
590 WCHAR *thisparam;
591 int argno = 0;
592 WCHAR *rawarg;
593 WIN32_FIND_DATAW fd;
594 HANDLE hff = INVALID_HANDLE_VALUE;
595 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
596 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
597 BOOL anyconcats = FALSE; /* Have we found any + options */
598 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
599 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
600 BOOL prompt; /* Prompt before overwriting */
601 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
602 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
603 BOOL status;
604 WCHAR copycmd[4];
605 DWORD len;
606 BOOL dstisdevice = FALSE;
607 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
609 typedef struct _COPY_FILES
611 struct _COPY_FILES *next;
612 BOOL concatenate;
613 WCHAR *name;
614 int binarycopy;
615 } COPY_FILES;
616 COPY_FILES *sourcelist = NULL;
617 COPY_FILES *lastcopyentry = NULL;
618 COPY_FILES *destination = NULL;
619 COPY_FILES *thiscopy = NULL;
620 COPY_FILES *prevcopy = NULL;
622 /* Assume we were successful! */
623 errorlevel = 0;
625 /* If no args supplied at all, report an error */
626 if (param1[0] == 0x00) {
627 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
628 errorlevel = 1;
629 return;
632 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
634 /* Walk through all args, building up a list of files to process */
635 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
636 while (*(thisparam)) {
637 WCHAR *pos1, *pos2;
638 BOOL inquotes;
640 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
642 /* Handle switches */
643 if (*thisparam == '/') {
644 while (*thisparam == '/') {
645 thisparam++;
646 if (toupperW(*thisparam) == 'D') {
647 opt_d = TRUE;
648 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
649 } else if (toupperW(*thisparam) == 'Y') {
650 opt_y = TRUE;
651 } else if (toupperW(*thisparam) == '-' && toupperW(*(thisparam+1)) == 'Y') {
652 opt_noty = TRUE;
653 } else if (toupperW(*thisparam) == 'V') {
654 opt_v = TRUE;
655 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
656 } else if (toupperW(*thisparam) == 'N') {
657 opt_n = TRUE;
658 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
659 } else if (toupperW(*thisparam) == 'Z') {
660 opt_z = TRUE;
661 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
662 } else if (toupperW(*thisparam) == 'A') {
663 if (binarymode != 0) {
664 binarymode = 0;
665 WINE_TRACE("Subsequent files will be handled as ASCII\n");
666 if (destination != NULL) {
667 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
668 destination->binarycopy = binarymode;
669 } else if (lastcopyentry != NULL) {
670 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
671 lastcopyentry->binarycopy = binarymode;
674 } else if (toupperW(*thisparam) == 'B') {
675 if (binarymode != 1) {
676 binarymode = 1;
677 WINE_TRACE("Subsequent files will be handled as binary\n");
678 if (destination != NULL) {
679 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
680 destination->binarycopy = binarymode;
681 } else if (lastcopyentry != NULL) {
682 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
683 lastcopyentry->binarycopy = binarymode;
686 } else {
687 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
689 thisparam++;
692 /* This parameter was purely switches, get the next one */
693 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
694 continue;
697 /* We have found something which is not a switch. If could be anything of the form
698 sourcefilename (which could be destination too)
699 + (when filename + filename syntex used)
700 sourcefilename+sourcefilename
701 +sourcefilename
702 +/b[tests show windows then ignores to end of parameter]
705 if (*thisparam=='+') {
706 if (lastcopyentry == NULL) {
707 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
708 errorlevel = 1;
709 goto exitreturn;
710 } else {
711 concatnextfilename = TRUE;
712 anyconcats = TRUE;
715 /* Move to next thing to process */
716 thisparam++;
717 if (*thisparam == 0x00)
718 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
719 continue;
722 /* We have found something to process - build a COPY_FILE block to store it */
723 thiscopy = heap_alloc(sizeof(COPY_FILES));
725 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
726 thiscopy->concatenate = concatnextfilename;
727 thiscopy->binarycopy = binarymode;
728 thiscopy->next = NULL;
730 /* Time to work out the name. Allocate at least enough space (deliberately too much to
731 leave space to append \* to the end) , then copy in character by character. Strip off
732 quotes if we find them. */
733 len = strlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
734 thiscopy->name = heap_alloc(len*sizeof(WCHAR));
735 memset(thiscopy->name, 0x00, len);
737 pos1 = thisparam;
738 pos2 = thiscopy->name;
739 inquotes = FALSE;
740 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
741 if (*pos1 == '"') {
742 inquotes = !inquotes;
743 pos1++;
744 } else *pos2++ = *pos1++;
746 *pos2 = 0;
747 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
749 /* This is either the first source, concatenated subsequent source or destination */
750 if (sourcelist == NULL) {
751 WINE_TRACE("Adding as first source part\n");
752 sourcelist = thiscopy;
753 lastcopyentry = thiscopy;
754 } else if (concatnextfilename) {
755 WINE_TRACE("Adding to source file list to be concatenated\n");
756 lastcopyentry->next = thiscopy;
757 lastcopyentry = thiscopy;
758 } else if (destination == NULL) {
759 destination = thiscopy;
760 } else {
761 /* We have processed sources and destinations and still found more to do - invalid */
762 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
763 errorlevel = 1;
764 goto exitreturn;
766 concatnextfilename = FALSE;
768 /* We either need to process the rest of the parameter or move to the next */
769 if (*pos1 == '/' || *pos1 == '+') {
770 thisparam = pos1;
771 continue;
772 } else {
773 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
777 /* Ensure we have at least one source file */
778 if (!sourcelist) {
779 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
780 errorlevel = 1;
781 goto exitreturn;
784 /* Default whether automatic overwriting is on. If we are interactive then
785 we prompt by default, otherwise we overwrite by default
786 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
787 if (opt_noty) prompt = TRUE;
788 else if (opt_y) prompt = FALSE;
789 else {
790 /* By default, we will force the overwrite in batch mode and ask for
791 * confirmation in interactive mode. */
792 prompt = interactive;
793 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
794 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
795 * default behavior. */
796 len = GetEnvironmentVariableW(copyCmdW, copycmd, ARRAY_SIZE(copycmd));
797 if (len && len < ARRAY_SIZE(copycmd)) {
798 if (!lstrcmpiW (copycmd, parmY))
799 prompt = FALSE;
800 else if (!lstrcmpiW (copycmd, parmNoY))
801 prompt = TRUE;
805 /* Calculate the destination now - if none supplied, it's current dir +
806 filename of first file in list*/
807 if (destination == NULL) {
809 WINE_TRACE("No destination supplied, so need to calculate it\n");
810 strcpyW(destname, dotW);
811 strcatW(destname, slashW);
813 destination = heap_alloc(sizeof(COPY_FILES));
814 if (destination == NULL) goto exitreturn;
815 destination->concatenate = FALSE; /* Not used for destination */
816 destination->binarycopy = binarymode;
817 destination->next = NULL; /* Not used for destination */
818 destination->name = NULL; /* To be filled in */
819 destisdirectory = TRUE;
821 } else {
822 WCHAR *filenamepart;
823 DWORD attributes;
825 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
827 /* Convert to fully qualified path/filename */
828 GetFullPathNameW(destination->name, ARRAY_SIZE(destname), destname, &filenamepart);
829 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
831 /* If parameter is a directory, ensure it ends in \ */
832 attributes = GetFileAttributesW(destname);
833 if (ends_with_backslash( destname ) ||
834 ((attributes != INVALID_FILE_ATTRIBUTES) &&
835 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
837 destisdirectory = TRUE;
838 if (!ends_with_backslash( destname )) strcatW(destname, slashW);
839 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
843 /* Normally, the destination is the current directory unless we are
844 concatenating, in which case it's current directory plus first filename.
845 Note that if the
846 In addition by default it is a binary copy unless concatenating, when
847 the copy defaults to an ascii copy (stop at EOF). We do not know the
848 first source part yet (until we search) so flag as needing filling in. */
850 if (anyconcats) {
851 /* We have found an a+b type syntax, so destination has to be a filename
852 and we need to default to ascii copying. If we have been supplied a
853 directory as the destination, we need to defer calculating the name */
854 if (destisdirectory) appendfirstsource = TRUE;
855 if (destination->binarycopy == -1) destination->binarycopy = 0;
857 } else if (!destisdirectory) {
858 /* We have been asked to copy to a filename. Default to ascii IF the
859 source contains wildcards (true even if only one match) */
860 if (strpbrkW(sourcelist->name, wildcardsW) != NULL) {
861 anyconcats = TRUE; /* We really are concatenating to a single file */
862 if (destination->binarycopy == -1) {
863 destination->binarycopy = 0;
865 } else {
866 if (destination->binarycopy == -1) {
867 destination->binarycopy = 1;
872 /* Save away the destination name*/
873 heap_free(destination->name);
874 destination->name = heap_strdupW(destname);
875 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
876 wine_dbgstr_w(destname), appendfirstsource);
878 /* Remember if the destination is a device */
879 if (strncmpW(destination->name, deviceW, strlenW(deviceW)) == 0) {
880 WINE_TRACE("Destination is a device\n");
881 dstisdevice = TRUE;
884 /* Now we need to walk the set of sources, and process each name we come to.
885 If anyconcats is true, we are writing to one file, otherwise we are using
886 the source name each time.
887 If destination exists, prompt for overwrite the first time (if concatenating
888 we ask each time until yes is answered)
889 The first source file we come across must exist (when wildcards expanded)
890 and if concatenating with overwrite prompts, each source file must exist
891 until a yes is answered. */
893 thiscopy = sourcelist;
894 prevcopy = NULL;
896 while (thiscopy != NULL) {
898 WCHAR srcpath[MAX_PATH];
899 const WCHAR *srcname;
900 WCHAR *filenamepart;
901 DWORD attributes;
902 BOOL srcisdevice = FALSE;
904 /* If it was not explicit, we now know whether we are concatenating or not and
905 hence whether to copy as binary or ascii */
906 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
908 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
909 to where the filename portion begins (used for wildcard expansion). */
910 GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
911 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
913 /* If parameter is a directory, ensure it ends in \* */
914 attributes = GetFileAttributesW(srcpath);
915 if (ends_with_backslash( srcpath )) {
917 /* We need to know where the filename part starts, so append * and
918 recalculate the full resulting path */
919 strcatW(thiscopy->name, starW);
920 GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
921 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
923 } else if ((strpbrkW(srcpath, wildcardsW) == NULL) &&
924 (attributes != INVALID_FILE_ATTRIBUTES) &&
925 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
927 /* We need to know where the filename part starts, so append \* and
928 recalculate the full resulting path */
929 strcatW(thiscopy->name, slashstarW);
930 GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
931 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
934 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
935 wine_dbgstr_w(srcpath), anyconcats);
937 /* If the source is a device, just use it, otherwise search */
938 if (strncmpW(srcpath, deviceW, strlenW(deviceW)) == 0) {
939 WINE_TRACE("Source is a device\n");
940 srcisdevice = TRUE;
941 srcname = &srcpath[4]; /* After the \\.\ prefix */
942 } else {
944 /* Loop through all source files */
945 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
946 hff = FindFirstFileW(srcpath, &fd);
947 if (hff != INVALID_HANDLE_VALUE) {
948 srcname = fd.cFileName;
952 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
953 do {
954 WCHAR outname[MAX_PATH];
955 BOOL overwrite;
956 BOOL appendtofirstfile = FALSE;
958 /* Skip . and .., and directories */
959 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
960 WINE_TRACE("Skipping directories\n");
961 } else {
963 /* Build final destination name */
964 strcpyW(outname, destination->name);
965 if (destisdirectory || appendfirstsource) strcatW(outname, srcname);
967 /* Build source name */
968 if (!srcisdevice) strcpyW(filenamepart, srcname);
970 /* Do we just overwrite (we do if we are writing to a device) */
971 overwrite = !prompt;
972 if (dstisdevice || (anyconcats && writtenoneconcat)) {
973 overwrite = TRUE;
976 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
977 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
978 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
979 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
981 if (!writtenoneconcat) {
982 appendtofirstfile = anyconcats && WCMD_IsSameFile(srcpath, outname);
985 /* Prompt before overwriting */
986 if (appendtofirstfile) {
987 overwrite = TRUE;
988 } else if (!overwrite) {
989 DWORD attributes = GetFileAttributesW(outname);
990 if (attributes != INVALID_FILE_ATTRIBUTES) {
991 WCHAR* question;
992 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
993 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
994 LocalFree(question);
996 else overwrite = TRUE;
999 /* If we needed to save away the first filename, do it */
1000 if (appendfirstsource && overwrite) {
1001 heap_free(destination->name);
1002 destination->name = heap_strdupW(outname);
1003 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
1004 appendfirstsource = FALSE;
1005 destisdirectory = FALSE;
1008 /* Do the copy as appropriate */
1009 if (overwrite) {
1010 if (anyconcats && WCMD_IsSameFile(srcpath, outname)) {
1011 /* Silently skip if the destination file is also a source file */
1012 status = TRUE;
1013 } else if (anyconcats && writtenoneconcat) {
1014 if (thiscopy->binarycopy) {
1015 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
1016 } else {
1017 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
1019 } else if (!thiscopy->binarycopy) {
1020 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
1021 } else if (srcisdevice) {
1022 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
1023 } else {
1024 status = CopyFileW(srcpath, outname, FALSE);
1026 if (!status) {
1027 WCMD_print_error ();
1028 errorlevel = 1;
1029 } else {
1030 WINE_TRACE("Copied successfully\n");
1031 if (anyconcats) writtenoneconcat = TRUE;
1033 /* Append EOF if ascii destination and we are not going to add more onto the end
1034 Note: Testing shows windows has an optimization whereas if you have a binary
1035 copy of a file to a single destination (ie concatenation) then it does not add
1036 the EOF, hence the check on the source copy type below. */
1037 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
1038 if (!WCMD_AppendEOF(outname)) {
1039 WCMD_print_error ();
1040 errorlevel = 1;
1046 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1047 if (!srcisdevice) FindClose (hff);
1048 } else {
1049 /* Error if the first file was not found */
1050 if (!anyconcats || !writtenoneconcat) {
1051 WCMD_print_error ();
1052 errorlevel = 1;
1056 /* Step on to the next supplied source */
1057 thiscopy = thiscopy -> next;
1060 /* Append EOF if ascii destination and we were concatenating */
1061 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
1062 if (!WCMD_AppendEOF(destination->name)) {
1063 WCMD_print_error ();
1064 errorlevel = 1;
1068 /* Exit out of the routine, freeing any remaining allocated memory */
1069 exitreturn:
1071 thiscopy = sourcelist;
1072 while (thiscopy != NULL) {
1073 prevcopy = thiscopy;
1074 /* Free up this block*/
1075 thiscopy = thiscopy -> next;
1076 heap_free(prevcopy->name);
1077 heap_free(prevcopy);
1080 /* Free up the destination memory */
1081 if (destination) {
1082 heap_free(destination->name);
1083 heap_free(destination);
1086 return;
1089 /****************************************************************************
1090 * WCMD_create_dir
1092 * Create a directory (and, if needed, any intermediate directories).
1094 * Modifies its argument by replacing slashes temporarily with nulls.
1097 static BOOL create_full_path(WCHAR* path)
1099 WCHAR *p, *start;
1101 /* don't mess with drive letter portion of path, if any */
1102 start = path;
1103 if (path[1] == ':')
1104 start = path+2;
1106 /* Strip trailing slashes. */
1107 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
1108 *p = 0;
1110 /* Step through path, creating intermediate directories as needed. */
1111 /* First component includes drive letter, if any. */
1112 p = start;
1113 for (;;) {
1114 DWORD rv;
1115 /* Skip to end of component */
1116 while (*p == '\\') p++;
1117 while (*p && *p != '\\') p++;
1118 if (!*p) {
1119 /* path is now the original full path */
1120 return CreateDirectoryW(path, NULL);
1122 /* Truncate path, create intermediate directory, and restore path */
1123 *p = 0;
1124 rv = CreateDirectoryW(path, NULL);
1125 *p = '\\';
1126 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1127 return FALSE;
1129 /* notreached */
1130 return FALSE;
1133 void WCMD_create_dir (WCHAR *args) {
1134 int argno = 0;
1135 WCHAR *argN = args;
1137 if (param1[0] == 0x00) {
1138 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1139 return;
1141 /* Loop through all args */
1142 while (TRUE) {
1143 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1144 if (!argN) break;
1145 if (!create_full_path(thisArg)) {
1146 WCMD_print_error ();
1147 errorlevel = 1;
1152 /* Parse the /A options given by the user on the commandline
1153 * into a bitmask of wanted attributes (*wantSet),
1154 * and a bitmask of unwanted attributes (*wantClear).
1156 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1157 static const WCHAR parmA[] = {'/','A','\0'};
1158 WCHAR *p;
1160 /* both are strictly 'out' parameters */
1161 *wantSet=0;
1162 *wantClear=0;
1164 /* For each /A argument */
1165 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
1166 /* Skip /A itself */
1167 p += 2;
1169 /* Skip optional : */
1170 if (*p == ':') p++;
1172 /* For each of the attribute specifier chars to this /A option */
1173 for (; *p != 0 && *p != '/'; p++) {
1174 BOOL negate = FALSE;
1175 DWORD mask = 0;
1177 if (*p == '-') {
1178 negate=TRUE;
1179 p++;
1182 /* Convert the attribute specifier to a bit in one of the masks */
1183 switch (*p) {
1184 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1185 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1186 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1187 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1188 default:
1189 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1191 if (negate)
1192 *wantClear |= mask;
1193 else
1194 *wantSet |= mask;
1199 /* If filename part of parameter is * or *.*,
1200 * and neither /Q nor /P options were given,
1201 * prompt the user whether to proceed.
1202 * Returns FALSE if user says no, TRUE otherwise.
1203 * *pPrompted is set to TRUE if the user is prompted.
1204 * (If /P supplied, del will prompt for individual files later.)
1206 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1207 static const WCHAR parmP[] = {'/','P','\0'};
1208 static const WCHAR parmQ[] = {'/','Q','\0'};
1210 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
1211 static const WCHAR anyExt[]= {'.','*','\0'};
1212 WCHAR drive[10];
1213 WCHAR dir[MAX_PATH];
1214 WCHAR fname[MAX_PATH];
1215 WCHAR ext[MAX_PATH];
1216 WCHAR fpath[MAX_PATH];
1218 /* Convert path into actual directory spec */
1219 GetFullPathNameW(filename, ARRAY_SIZE(fpath), fpath, NULL);
1220 WCMD_splitpath(fpath, drive, dir, fname, ext);
1222 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1223 if ((strcmpW(fname, starW) == 0) &&
1224 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
1226 WCHAR question[MAXSTRING];
1227 static const WCHAR fmt[] = {'%','s',' ','\0'};
1229 /* Caller uses this to suppress "file not found" warning later */
1230 *pPrompted = TRUE;
1232 /* Ask for confirmation */
1233 wsprintfW(question, fmt, fpath);
1234 return WCMD_ask_confirm(question, TRUE, NULL);
1237 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1238 return TRUE;
1241 /* Helper function for WCMD_delete().
1242 * Deletes a single file, directory, or wildcard.
1243 * If /S was given, does it recursively.
1244 * Returns TRUE if a file was deleted.
1246 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1248 static const WCHAR parmP[] = {'/','P','\0'};
1249 static const WCHAR parmS[] = {'/','S','\0'};
1250 static const WCHAR parmF[] = {'/','F','\0'};
1251 DWORD wanted_attrs;
1252 DWORD unwanted_attrs;
1253 BOOL found = FALSE;
1254 WCHAR argCopy[MAX_PATH];
1255 WIN32_FIND_DATAW fd;
1256 HANDLE hff;
1257 WCHAR fpath[MAX_PATH];
1258 WCHAR *p;
1259 BOOL handleParm = TRUE;
1261 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1263 strcpyW(argCopy, thisArg);
1264 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1265 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1267 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1268 /* Skip this arg if user declines to delete *.* */
1269 return FALSE;
1272 /* First, try to delete in the current directory */
1273 hff = FindFirstFileW(argCopy, &fd);
1274 if (hff == INVALID_HANDLE_VALUE) {
1275 handleParm = FALSE;
1276 } else {
1277 found = TRUE;
1280 /* Support del <dirname> by just deleting all files dirname\* */
1281 if (handleParm
1282 && (strchrW(argCopy,'*') == NULL)
1283 && (strchrW(argCopy,'?') == NULL)
1284 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1286 WCHAR modifiedParm[MAX_PATH];
1287 static const WCHAR slashStar[] = {'\\','*','\0'};
1289 strcpyW(modifiedParm, argCopy);
1290 strcatW(modifiedParm, slashStar);
1291 FindClose(hff);
1292 found = TRUE;
1293 WCMD_delete_one(modifiedParm);
1295 } else if (handleParm) {
1297 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1298 strcpyW (fpath, argCopy);
1299 do {
1300 p = strrchrW (fpath, '\\');
1301 if (p != NULL) {
1302 *++p = '\0';
1303 strcatW (fpath, fd.cFileName);
1305 else strcpyW (fpath, fd.cFileName);
1306 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1307 BOOL ok;
1309 /* Handle attribute matching (/A) */
1310 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1311 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1313 /* /P means prompt for each file */
1314 if (ok && strstrW (quals, parmP) != NULL) {
1315 WCHAR* question;
1317 /* Ask for confirmation */
1318 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1319 ok = WCMD_ask_confirm(question, FALSE, NULL);
1320 LocalFree(question);
1323 /* Only proceed if ok to */
1324 if (ok) {
1326 /* If file is read only, and /A:r or /F supplied, delete it */
1327 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1328 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1329 strstrW (quals, parmF) != NULL)) {
1330 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1333 /* Now do the delete */
1334 if (!DeleteFileW(fpath)) WCMD_print_error ();
1338 } while (FindNextFileW(hff, &fd) != 0);
1339 FindClose (hff);
1342 /* Now recurse into all subdirectories handling the parameter in the same way */
1343 if (strstrW (quals, parmS) != NULL) {
1345 WCHAR thisDir[MAX_PATH];
1346 int cPos;
1348 WCHAR drive[10];
1349 WCHAR dir[MAX_PATH];
1350 WCHAR fname[MAX_PATH];
1351 WCHAR ext[MAX_PATH];
1353 /* Convert path into actual directory spec */
1354 GetFullPathNameW(argCopy, ARRAY_SIZE(thisDir), thisDir, NULL);
1355 WCMD_splitpath(thisDir, drive, dir, fname, ext);
1357 strcpyW(thisDir, drive);
1358 strcatW(thisDir, dir);
1359 cPos = strlenW(thisDir);
1361 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1363 /* Append '*' to the directory */
1364 thisDir[cPos] = '*';
1365 thisDir[cPos+1] = 0x00;
1367 hff = FindFirstFileW(thisDir, &fd);
1369 /* Remove residual '*' */
1370 thisDir[cPos] = 0x00;
1372 if (hff != INVALID_HANDLE_VALUE) {
1373 DIRECTORY_STACK *allDirs = NULL;
1374 DIRECTORY_STACK *lastEntry = NULL;
1376 do {
1377 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1378 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1379 (strcmpW(fd.cFileName, dotW) != 0)) {
1381 DIRECTORY_STACK *nextDir;
1382 WCHAR subParm[MAX_PATH];
1384 /* Work out search parameter in sub dir */
1385 strcpyW (subParm, thisDir);
1386 strcatW (subParm, fd.cFileName);
1387 strcatW (subParm, slashW);
1388 strcatW (subParm, fname);
1389 strcatW (subParm, ext);
1390 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1392 /* Allocate memory, add to list */
1393 nextDir = heap_alloc(sizeof(DIRECTORY_STACK));
1394 if (allDirs == NULL) allDirs = nextDir;
1395 if (lastEntry != NULL) lastEntry->next = nextDir;
1396 lastEntry = nextDir;
1397 nextDir->next = NULL;
1398 nextDir->dirName = heap_strdupW(subParm);
1400 } while (FindNextFileW(hff, &fd) != 0);
1401 FindClose (hff);
1403 /* Go through each subdir doing the delete */
1404 while (allDirs != NULL) {
1405 DIRECTORY_STACK *tempDir;
1407 tempDir = allDirs->next;
1408 found |= WCMD_delete_one (allDirs->dirName);
1410 heap_free(allDirs->dirName);
1411 heap_free(allDirs);
1412 allDirs = tempDir;
1417 return found;
1420 /****************************************************************************
1421 * WCMD_delete
1423 * Delete a file or wildcarded set.
1425 * Note on /A:
1426 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1427 * - Each set is a pattern, eg /ahr /as-r means
1428 * readonly+hidden OR nonreadonly system files
1429 * - The '-' applies to a single field, ie /a:-hr means read only
1430 * non-hidden files
1433 BOOL WCMD_delete (WCHAR *args) {
1434 int argno;
1435 WCHAR *argN;
1436 BOOL argsProcessed = FALSE;
1437 BOOL foundAny = FALSE;
1439 errorlevel = 0;
1441 for (argno=0; ; argno++) {
1442 BOOL found;
1443 WCHAR *thisArg;
1445 argN = NULL;
1446 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1447 if (!argN)
1448 break; /* no more parameters */
1449 if (argN[0] == '/')
1450 continue; /* skip options */
1452 argsProcessed = TRUE;
1453 found = WCMD_delete_one(thisArg);
1454 if (!found)
1455 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1456 foundAny |= found;
1459 /* Handle no valid args */
1460 if (!argsProcessed)
1461 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1463 return foundAny;
1467 * WCMD_strtrim
1469 * Returns a trimmed version of s with all leading and trailing whitespace removed
1470 * Pre: s non NULL
1473 static WCHAR *WCMD_strtrim(const WCHAR *s)
1475 DWORD len = strlenW(s);
1476 const WCHAR *start = s;
1477 WCHAR* result;
1479 result = heap_alloc((len + 1) * sizeof(WCHAR));
1481 while (isspaceW(*start)) start++;
1482 if (*start) {
1483 const WCHAR *end = s + len - 1;
1484 while (end > start && isspaceW(*end)) end--;
1485 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1486 result[end - start + 1] = '\0';
1487 } else {
1488 result[0] = '\0';
1491 return result;
1494 /****************************************************************************
1495 * WCMD_echo
1497 * Echo input to the screen (or not). We don't try to emulate the bugs
1498 * in DOS (try typing "ECHO ON AGAIN" for an example).
1501 void WCMD_echo (const WCHAR *args)
1503 int count;
1504 const WCHAR *origcommand = args;
1505 WCHAR *trimmed;
1507 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1508 || args[0]==':' || args[0]==';' || args[0]=='/')
1509 args++;
1511 trimmed = WCMD_strtrim(args);
1512 if (!trimmed) return;
1514 count = strlenW(trimmed);
1515 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1516 && origcommand[0]!=';' && origcommand[0]!='/') {
1517 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
1518 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
1519 heap_free(trimmed);
1520 return;
1523 if (lstrcmpiW(trimmed, onW) == 0)
1524 echo_mode = TRUE;
1525 else if (lstrcmpiW(trimmed, offW) == 0)
1526 echo_mode = FALSE;
1527 else {
1528 WCMD_output_asis (args);
1529 WCMD_output_asis (newlineW);
1531 heap_free(trimmed);
1534 /*****************************************************************************
1535 * WCMD_part_execute
1537 * Execute a command, and any && or bracketed follow on to the command. The
1538 * first command to be executed may not be at the front of the
1539 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1541 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1542 BOOL isIF, BOOL executecmds)
1544 CMD_LIST *curPosition = *cmdList;
1545 int myDepth = (*cmdList)->bracketDepth;
1547 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d), isIF(%d)\n", cmdList,
1548 wine_dbgstr_w(firstcmd), executecmds, isIF);
1550 /* Skip leading whitespace between condition and the command */
1551 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1553 /* Process the first command, if there is one */
1554 if (executecmds && firstcmd && *firstcmd) {
1555 WCHAR *command = heap_strdupW(firstcmd);
1556 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1557 heap_free(command);
1561 /* If it didn't move the position, step to next command */
1562 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1564 /* Process any other parts of the command */
1565 if (*cmdList) {
1566 BOOL processThese = executecmds;
1568 while (*cmdList) {
1569 static const WCHAR ifElse[] = {'e','l','s','e'};
1571 /* execute all appropriate commands */
1572 curPosition = *cmdList;
1574 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d) processThese(%d)\n",
1575 *cmdList,
1576 (*cmdList)->prevDelim,
1577 (*cmdList)->bracketDepth,
1578 myDepth,
1579 processThese);
1581 /* Execute any statements appended to the line */
1582 /* FIXME: Only if previous call worked for && or failed for || */
1583 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1584 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1585 if (processThese && (*cmdList)->command) {
1586 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1587 cmdList, FALSE);
1589 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1591 /* Execute any appended to the statement with (...) */
1592 } else if ((*cmdList)->bracketDepth > myDepth) {
1593 if (processThese) {
1594 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1595 } else {
1596 WINE_TRACE("Skipping command %p due to stack depth\n", *cmdList);
1598 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1600 /* End of the command - does 'ELSE ' follow as the next command? */
1601 } else {
1602 if (isIF && WCMD_keyword_ws_found(ifElse, ARRAY_SIZE(ifElse), (*cmdList)->command)) {
1603 /* Swap between if and else processing */
1604 processThese = !executecmds;
1606 /* Process the ELSE part */
1607 if (processThese) {
1608 const int keyw_len = ARRAY_SIZE(ifElse) + 1;
1609 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1611 /* Skip leading whitespace between condition and the command */
1612 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1613 if (*cmd) {
1614 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1616 } else {
1617 /* Loop skipping all commands until we get back to the current
1618 depth, including skipping commands and their subsequent
1619 pipes (eg cmd | prog) */
1620 do {
1621 *cmdList = (*cmdList)->nextcommand;
1622 } while (*cmdList &&
1623 ((*cmdList)->bracketDepth > myDepth ||
1624 (*cmdList)->prevDelim));
1626 /* After the else is complete, we need to now process subsequent commands */
1627 processThese = TRUE;
1629 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1631 /* If we were in an IF statement and we didn't find an else and yet we get back to
1632 the same bracket depth as the IF, then the IF statement is over. This is required
1633 to handle nested ifs properly */
1634 } else if (isIF && (*cmdList)->bracketDepth == myDepth) {
1635 WINE_TRACE("Found end of this nested IF statement, ending this if\n");
1636 break;
1637 } else if (!processThese) {
1638 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1639 WINE_TRACE("Skipping this command, as in not process mode (next = %p)\n", *cmdList);
1640 } else {
1641 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1642 break;
1647 return;
1650 /*****************************************************************************
1651 * WCMD_parse_forf_options
1653 * Parses the for /f 'options', extracting the values and validating the
1654 * keywords. Note all keywords are optional.
1655 * Parameters:
1656 * options [I] The unparsed parameter string
1657 * eol [O] Set to the comment character (eol=x)
1658 * skip [O] Set to the number of lines to skip (skip=xx)
1659 * delims [O] Set to the token delimiters (delims=)
1660 * tokens [O] Set to the requested tokens, as provided (tokens=)
1661 * usebackq [O] Set to TRUE if usebackq found
1663 * Returns TRUE on success, FALSE on syntax error
1666 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1667 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1670 WCHAR *pos = options;
1671 int len = strlenW(pos);
1672 static const WCHAR eolW[] = {'e','o','l','='};
1673 static const WCHAR skipW[] = {'s','k','i','p','='};
1674 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1675 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1676 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1677 static const WCHAR forf_defaultdelims[] = {' ', '\t', '\0'};
1678 static const WCHAR forf_defaulttokens[] = {'1', '\0'};
1680 /* Initialize to defaults */
1681 strcpyW(delims, forf_defaultdelims);
1682 strcpyW(tokens, forf_defaulttokens);
1683 *eol = 0;
1684 *skip = 0;
1685 *usebackq = FALSE;
1687 /* Strip (optional) leading and trailing quotes */
1688 if ((*pos == '"') && (pos[len-1] == '"')) {
1689 pos[len-1] = 0;
1690 pos++;
1693 /* Process each keyword */
1694 while (pos && *pos) {
1695 if (*pos == ' ' || *pos == '\t') {
1696 pos++;
1698 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1699 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1700 pos, ARRAY_SIZE(eolW), eolW, ARRAY_SIZE(eolW)) == CSTR_EQUAL) {
1701 *eol = *(pos + ARRAY_SIZE(eolW));
1702 pos = pos + ARRAY_SIZE(eolW) + 1;
1703 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1705 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1706 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1707 pos, ARRAY_SIZE(skipW), skipW, ARRAY_SIZE(skipW)) == CSTR_EQUAL) {
1708 WCHAR *nextchar = NULL;
1709 pos = pos + ARRAY_SIZE(skipW);
1710 *skip = strtoulW(pos, &nextchar, 0);
1711 WINE_TRACE("Found skip as %d lines\n", *skip);
1712 pos = nextchar;
1714 /* Save if usebackq semantics are in effect */
1715 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, pos,
1716 ARRAY_SIZE(usebackqW), usebackqW, ARRAY_SIZE(usebackqW)) == CSTR_EQUAL) {
1717 *usebackq = TRUE;
1718 pos = pos + ARRAY_SIZE(usebackqW);
1719 WINE_TRACE("Found usebackq\n");
1721 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1722 if you finish the optionsroot string with delims= otherwise the space is
1723 just a token delimiter! */
1724 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1725 pos, ARRAY_SIZE(delimsW), delimsW, ARRAY_SIZE(delimsW)) == CSTR_EQUAL) {
1726 int i=0;
1728 pos = pos + ARRAY_SIZE(delimsW);
1729 while (*pos && *pos != ' ') {
1730 delims[i++] = *pos;
1731 pos++;
1733 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1734 delims[i++] = 0; /* Null terminate the delims */
1735 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1737 /* Save the tokens being requested */
1738 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1739 pos, ARRAY_SIZE(tokensW), tokensW, ARRAY_SIZE(tokensW)) == CSTR_EQUAL) {
1740 int i=0;
1742 pos = pos + ARRAY_SIZE(tokensW);
1743 while (*pos && *pos != ' ') {
1744 tokens[i++] = *pos;
1745 pos++;
1747 tokens[i++] = 0; /* Null terminate the tokens */
1748 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1750 } else {
1751 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1752 return FALSE;
1755 return TRUE;
1758 /*****************************************************************************
1759 * WCMD_add_dirstowalk
1761 * When recursing through directories (for /r), we need to add to the list of
1762 * directories still to walk, any subdirectories of the one we are processing.
1764 * Parameters
1765 * options [I] The remaining list of directories still to process
1767 * Note this routine inserts the subdirectories found between the entry being
1768 * processed, and any other directory still to be processed, mimicking what
1769 * Windows does
1771 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1772 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1773 WCHAR fullitem[MAX_PATH];
1774 WIN32_FIND_DATAW fd;
1775 HANDLE hff;
1777 /* Build a generic search and add all directories on the list of directories
1778 still to walk */
1779 strcpyW(fullitem, dirsToWalk->dirName);
1780 strcatW(fullitem, slashstarW);
1781 hff = FindFirstFileW(fullitem, &fd);
1782 if (hff != INVALID_HANDLE_VALUE) {
1783 do {
1784 WINE_TRACE("Looking for subdirectories\n");
1785 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1786 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1787 (strcmpW(fd.cFileName, dotW) != 0))
1789 /* Allocate memory, add to list */
1790 DIRECTORY_STACK *toWalk = heap_alloc(sizeof(DIRECTORY_STACK));
1791 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1792 toWalk->next = remainingDirs->next;
1793 remainingDirs->next = toWalk;
1794 remainingDirs = toWalk;
1795 toWalk->dirName = heap_alloc(sizeof(WCHAR) * (strlenW(dirsToWalk->dirName) + 2 + strlenW(fd.cFileName)));
1796 strcpyW(toWalk->dirName, dirsToWalk->dirName);
1797 strcatW(toWalk->dirName, slashW);
1798 strcatW(toWalk->dirName, fd.cFileName);
1799 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1800 toWalk, toWalk->next);
1802 } while (FindNextFileW(hff, &fd) != 0);
1803 WINE_TRACE("Finished adding all subdirectories\n");
1804 FindClose (hff);
1808 /**************************************************************************
1809 * WCMD_for_nexttoken
1811 * Parse the token= line, identifying the next highest number not processed
1812 * so far. Count how many tokens are referred (including duplicates) and
1813 * optionally return that, plus optionally indicate if the tokens= line
1814 * ends in a star.
1816 * Parameters:
1817 * lasttoken [I] - Identifies the token index of the last one
1818 * returned so far (-1 used for first loop)
1819 * tokenstr [I] - The specified tokens= line
1820 * firstCmd [O] - Optionally indicate how many tokens are listed
1821 * doAll [O] - Optionally indicate if line ends with *
1822 * duplicates [O] - Optionally indicate if there is any evidence of
1823 * overlaying tokens in the string
1824 * Note the caller should keep a running track of duplicates as the tokens
1825 * are recursively passed. If any have duplicates, then the * token should
1826 * not be honoured.
1828 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1829 int *totalfound, BOOL *doall,
1830 BOOL *duplicates)
1832 WCHAR *pos = tokenstr;
1833 int nexttoken = -1;
1835 if (totalfound) *totalfound = 0;
1836 if (doall) *doall = FALSE;
1837 if (duplicates) *duplicates = FALSE;
1839 WINE_TRACE("Find next token after %d in %s\n", lasttoken,
1840 wine_dbgstr_w(tokenstr));
1842 /* Loop through the token string, parsing it. Valid syntax is:
1843 token=m or x-y with comma delimiter and optionally * to finish*/
1844 while (*pos) {
1845 int nextnumber1, nextnumber2 = -1;
1846 WCHAR *nextchar;
1848 /* Remember if the next character is a star, it indicates a need to
1849 show all remaining tokens and should be the last character */
1850 if (*pos == '*') {
1851 if (doall) *doall = TRUE;
1852 if (totalfound) (*totalfound)++;
1853 /* If we have not found a next token to return, then indicate
1854 time to process the star */
1855 if (nexttoken == -1) {
1856 if (lasttoken == -1) {
1857 /* Special case the syntax of tokens=* which just means get whole line */
1858 nexttoken = 0;
1859 } else {
1860 nexttoken = lasttoken;
1863 break;
1866 /* Get the next number */
1867 nextnumber1 = strtoulW(pos, &nextchar, 10);
1869 /* If it is followed by a minus, it's a range, so get the next one as well */
1870 if (*nextchar == '-') {
1871 nextnumber2 = strtoulW(nextchar+1, &nextchar, 10);
1873 /* We want to return the lowest number that is higher than lasttoken
1874 but only if range is positive */
1875 if (nextnumber2 >= nextnumber1 &&
1876 lasttoken < nextnumber2) {
1878 int nextvalue;
1879 if (nexttoken == -1) {
1880 nextvalue = max(nextnumber1, (lasttoken+1));
1881 } else {
1882 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1885 /* Flag if duplicates identified */
1886 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1888 nexttoken = nextvalue;
1891 /* Update the running total for the whole range */
1892 if (nextnumber2 >= nextnumber1 && totalfound) {
1893 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1895 pos = nextchar;
1897 } else if (pos != nextchar) {
1898 if (totalfound) (*totalfound)++;
1900 /* See if the number found is one we have already seen */
1901 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1903 /* We want to return the lowest number that is higher than lasttoken */
1904 if (lasttoken < nextnumber1 &&
1905 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1906 nexttoken = nextnumber1;
1908 pos = nextchar;
1910 } else {
1911 /* Step on to the next character, usually over comma */
1912 if (*pos) pos++;
1917 /* Return result */
1918 if (nexttoken == -1) {
1919 WINE_TRACE("No next token found, previous was %d\n", lasttoken);
1920 nexttoken = lasttoken;
1921 } else if (nexttoken==lasttoken && doall && *doall) {
1922 WINE_TRACE("Request for all remaining tokens now\n");
1923 } else {
1924 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1926 if (totalfound) WINE_TRACE("Found total tokens to be %d\n", *totalfound);
1927 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1928 return nexttoken;
1931 /**************************************************************************
1932 * WCMD_parse_line
1934 * When parsing file or string contents (for /f), once the string to parse
1935 * has been identified, handle the various options and call the do part
1936 * if appropriate.
1938 * Parameters:
1939 * cmdStart [I] - Identifies the list of commands making up the
1940 * for loop body (especially if brackets in use)
1941 * firstCmd [I] - The textual start of the command after the DO
1942 * which is within the first item of cmdStart
1943 * cmdEnd [O] - Identifies where to continue after the DO
1944 * variable [I] - The variable identified on the for line
1945 * buffer [I] - The string to parse
1946 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1947 * forf_skip [I/O] - How many lines to skip first
1948 * forf_eol [I] - The 'end of line' (comment) character
1949 * forf_delims [I] - The delimiters to use when breaking the string apart
1950 * forf_tokens [I] - The tokens to use when breaking the string apart
1952 static void WCMD_parse_line(CMD_LIST *cmdStart,
1953 const WCHAR *firstCmd,
1954 CMD_LIST **cmdEnd,
1955 const WCHAR variable,
1956 WCHAR *buffer,
1957 BOOL *doExecuted,
1958 int *forf_skip,
1959 WCHAR forf_eol,
1960 WCHAR *forf_delims,
1961 WCHAR *forf_tokens) {
1963 WCHAR *parm;
1964 FOR_CONTEXT oldcontext;
1965 int varidx, varoffset;
1966 int nexttoken, lasttoken = -1;
1967 BOOL starfound = FALSE;
1968 BOOL thisduplicate = FALSE;
1969 BOOL anyduplicates = FALSE;
1970 int totalfound;
1972 /* Skip lines if requested */
1973 if (*forf_skip) {
1974 (*forf_skip)--;
1975 return;
1978 /* Save away any existing for variable context (e.g. nested for loops) */
1979 oldcontext = forloopcontext;
1981 /* Extract the parameters based on the tokens= value (There will always
1982 be some value, as if it is not supplied, it defaults to tokens=1).
1983 Rough logic:
1984 Count how many tokens are named in the line, identify the lowest
1985 Empty (set to null terminated string) that number of named variables
1986 While lasttoken != nextlowest
1987 %letter = parameter number 'nextlowest'
1988 letter++ (if >26 or >52 abort)
1989 Go through token= string finding next lowest number
1990 If token ends in * set %letter = raw position of token(nextnumber+1)
1992 lasttoken = -1;
1993 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1994 &starfound, &thisduplicate);
1995 varidx = FOR_VAR_IDX(variable);
1997 /* Empty out variables */
1998 for (varoffset=0;
1999 varidx >= 0 && varoffset<totalfound && (((varidx%26) + varoffset) < 26);
2000 varoffset++) {
2001 forloopcontext.variable[varidx + varoffset] = (WCHAR *)nullW;
2004 /* Loop extracting the tokens
2005 Note: nexttoken of 0 means there were no tokens requested, to handle
2006 the special case of tokens=* */
2007 varoffset = 0;
2008 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
2009 while (varidx >= 0 && (nexttoken > 0 && (nexttoken > lasttoken))) {
2010 anyduplicates |= thisduplicate;
2012 /* Extract the token number requested and set into the next variable context */
2013 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims);
2014 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
2015 varidx + varoffset, wine_dbgstr_w(parm));
2016 if (varidx >=0) {
2017 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2018 varoffset++;
2019 if (((varidx%26)+varoffset) >= 26) break;
2022 /* Find the next token */
2023 lasttoken = nexttoken;
2024 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
2025 &starfound, &thisduplicate);
2028 /* If all the rest of the tokens were requested, and there is still space in
2029 the variable range, write them now */
2030 if (!anyduplicates && starfound && varidx >= 0 && (((varidx%26) + varoffset) < 26)) {
2031 nexttoken++;
2032 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
2033 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2034 varidx + varoffset, wine_dbgstr_w(parm));
2035 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2038 /* Execute the body of the foor loop with these values */
2039 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
2040 CMD_LIST *thisCmdStart = cmdStart;
2041 *doExecuted = TRUE;
2042 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
2043 *cmdEnd = thisCmdStart;
2046 /* Free the duplicated strings, and restore the context */
2047 if (varidx >=0) {
2048 int i;
2049 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
2050 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
2051 (forloopcontext.variable[i] != nullW)) {
2052 heap_free(forloopcontext.variable[i]);
2057 /* Restore the original for variable contextx */
2058 forloopcontext = oldcontext;
2061 /**************************************************************************
2062 * WCMD_forf_getinputhandle
2064 * Return a file handle which can be used for reading the input lines,
2065 * either to a specific file (which may be quote delimited as we have to
2066 * read the parameters in raw mode) or to a command which we need to
2067 * execute. The command being executed runs in its own shell and stores
2068 * its data in a temporary file.
2070 * Parameters:
2071 * usebackq [I] - Indicates whether usebackq is in effect or not
2072 * itemStr [I] - The item to be handled, either a filename or
2073 * whole command string to execute
2074 * iscmd [I] - Identifies whether this is a command or not
2076 * Returns a file handle which can be used to read the input lines from.
2078 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2079 WCHAR temp_str[MAX_PATH];
2080 WCHAR temp_file[MAX_PATH];
2081 WCHAR temp_cmd[MAXSTRING];
2082 WCHAR *trimmed = NULL;
2083 HANDLE hinput = INVALID_HANDLE_VALUE;
2084 static const WCHAR redirOutW[] = {'>','%','s','\0'};
2085 static const WCHAR cmdW[] = {'C','M','D','\0'};
2086 static const WCHAR cmdslashcW[] = {'C','M','D','.','E','X','E',' ',
2087 '/','C',' ','%','s','\0'};
2089 /* Remove leading and trailing character (but there may be trailing whitespace too) */
2090 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2091 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2092 (!iscmd && (itemstr[0] == '"' && usebackq)))
2094 trimmed = WCMD_strtrim(itemstr);
2095 if (trimmed) {
2096 itemstr = trimmed;
2098 itemstr[strlenW(itemstr)-1] = 0x00;
2099 itemstr++;
2102 if (iscmd) {
2103 /* Get temp filename */
2104 GetTempPathW(ARRAY_SIZE(temp_str), temp_str);
2105 GetTempFileNameW(temp_str, cmdW, 0, temp_file);
2107 /* Redirect output to the temporary file */
2108 wsprintfW(temp_str, redirOutW, temp_file);
2109 wsprintfW(temp_cmd, cmdslashcW, itemstr);
2110 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2111 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2112 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2114 /* Open the file, read line by line and process */
2115 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2116 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2118 } else {
2119 /* Open the file, read line by line and process */
2120 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2121 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2122 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2124 heap_free(trimmed);
2125 return hinput;
2128 /**************************************************************************
2129 * WCMD_for
2131 * Batch file loop processing.
2133 * On entry: cmdList contains the syntax up to the set
2134 * next cmdList and all in that bracket contain the set data
2135 * next cmdlist contains the DO cmd
2136 * following that is either brackets or && entries (as per if)
2140 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2142 WIN32_FIND_DATAW fd;
2143 HANDLE hff;
2144 int i;
2145 static const WCHAR inW[] = {'i','n'};
2146 static const WCHAR doW[] = {'d','o'};
2147 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2148 WCHAR variable[4];
2149 int varidx = -1;
2150 WCHAR *oldvariablevalue;
2151 WCHAR *firstCmd;
2152 int thisDepth;
2153 WCHAR optionsRoot[MAX_PATH];
2154 DIRECTORY_STACK *dirsToWalk = NULL;
2155 BOOL expandDirs = FALSE;
2156 BOOL useNumbers = FALSE;
2157 BOOL doFileset = FALSE;
2158 BOOL doRecurse = FALSE;
2159 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2160 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2161 int itemNum;
2162 CMD_LIST *thisCmdStart;
2163 int parameterNo = 0;
2164 WCHAR forf_eol = 0;
2165 int forf_skip = 0;
2166 WCHAR forf_delims[256];
2167 WCHAR forf_tokens[MAXSTRING];
2168 BOOL forf_usebackq = FALSE;
2170 /* Handle optional qualifiers (multiple are allowed) */
2171 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2173 optionsRoot[0] = 0;
2174 while (thisArg && *thisArg == '/') {
2175 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2176 thisArg++;
2177 switch (toupperW(*thisArg)) {
2178 case 'D': expandDirs = TRUE; break;
2179 case 'L': useNumbers = TRUE; break;
2181 /* Recursive is special case - /R can have an optional path following it */
2182 /* filenamesets are another special case - /F can have an optional options following it */
2183 case 'R':
2184 case 'F':
2186 /* When recursing directories, use current directory as the starting point unless
2187 subsequently overridden */
2188 doRecurse = (toupperW(*thisArg) == 'R');
2189 if (doRecurse) GetCurrentDirectoryW(ARRAY_SIZE(optionsRoot), optionsRoot);
2191 doFileset = (toupperW(*thisArg) == 'F');
2193 /* Retrieve next parameter to see if is root/options (raw form required
2194 with for /f, or unquoted in for /r) */
2195 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2197 /* Next parm is either qualifier, path/options or variable -
2198 only care about it if it is the path/options */
2199 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2200 parameterNo++;
2201 strcpyW(optionsRoot, thisArg);
2203 break;
2205 default:
2206 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2209 /* Step to next token */
2210 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2213 /* Ensure line continues with variable */
2214 if (*thisArg != '%') {
2215 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2216 return;
2219 /* With for /f parse the options if provided */
2220 if (doFileset) {
2221 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2222 forf_delims, forf_tokens, &forf_usebackq))
2224 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2225 return;
2228 /* Set up the list of directories to recurse if we are going to */
2229 } else if (doRecurse) {
2230 /* Allocate memory, add to list */
2231 dirsToWalk = heap_alloc(sizeof(DIRECTORY_STACK));
2232 dirsToWalk->next = NULL;
2233 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2234 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2237 /* Variable should follow */
2238 strcpyW(variable, thisArg);
2239 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2240 varidx = FOR_VAR_IDX(variable[1]);
2242 /* Ensure line continues with IN */
2243 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2244 if (!thisArg
2245 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2246 thisArg, ARRAY_SIZE(inW), inW, ARRAY_SIZE(inW)) == CSTR_EQUAL)) {
2247 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2248 return;
2251 /* Save away where the set of data starts and the variable */
2252 thisDepth = (*cmdList)->bracketDepth;
2253 *cmdList = (*cmdList)->nextcommand;
2254 setStart = (*cmdList);
2256 /* Skip until the close bracket */
2257 WINE_TRACE("Searching %p as the set\n", *cmdList);
2258 while (*cmdList &&
2259 (*cmdList)->command != NULL &&
2260 (*cmdList)->bracketDepth > thisDepth) {
2261 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2262 *cmdList = (*cmdList)->nextcommand;
2265 /* Skip the close bracket, if there is one */
2266 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2268 /* Syntax error if missing close bracket, or nothing following it
2269 and once we have the complete set, we expect a DO */
2270 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2271 if ((*cmdList == NULL) || !WCMD_keyword_ws_found(doW, ARRAY_SIZE(doW), (*cmdList)->command)) {
2272 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2273 return;
2276 cmdEnd = *cmdList;
2278 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2279 mode, or once for the rest of the time. */
2280 do {
2282 /* Save away the starting position for the commands (and offset for the
2283 first one) */
2284 cmdStart = *cmdList;
2285 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2286 itemNum = 0;
2288 /* If we are recursing directories (ie /R), add all sub directories now, then
2289 prefix the root when searching for the item */
2290 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2292 thisSet = setStart;
2293 /* Loop through all set entries */
2294 while (thisSet &&
2295 thisSet->command != NULL &&
2296 thisSet->bracketDepth >= thisDepth) {
2298 /* Loop through all entries on the same line */
2299 WCHAR *staticitem;
2300 WCHAR *itemStart;
2301 WCHAR buffer[MAXSTRING];
2303 WINE_TRACE("Processing for set %p\n", thisSet);
2304 i = 0;
2305 while (*(staticitem = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2308 * If the parameter within the set has a wildcard then search for matching files
2309 * otherwise do a literal substitution.
2311 static const WCHAR wildcards[] = {'*','?','\0'};
2313 /* Take a copy of the item returned from WCMD_parameter as it is held in a
2314 static buffer which can be overwritten during parsing of the for body */
2315 WCHAR item[MAXSTRING];
2316 strcpyW(item, staticitem);
2318 thisCmdStart = cmdStart;
2320 itemNum++;
2321 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2323 if (!useNumbers && !doFileset) {
2324 WCHAR fullitem[MAX_PATH];
2325 int prefixlen = 0;
2327 /* Now build the item to use / search for in the specified directory,
2328 as it is fully qualified in the /R case */
2329 if (dirsToWalk) {
2330 strcpyW(fullitem, dirsToWalk->dirName);
2331 strcatW(fullitem, slashW);
2332 strcatW(fullitem, item);
2333 } else {
2334 WCHAR *prefix = strrchrW(item, '\\');
2335 if (prefix) prefixlen = (prefix - item) + 1;
2336 strcpyW(fullitem, item);
2339 if (strpbrkW (fullitem, wildcards)) {
2340 hff = FindFirstFileW(fullitem, &fd);
2341 if (hff != INVALID_HANDLE_VALUE) {
2342 do {
2343 BOOL isDirectory = FALSE;
2345 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2347 /* Handle as files or dirs appropriately, but ignore . and .. */
2348 if (isDirectory == expandDirs &&
2349 (strcmpW(fd.cFileName, dotdotW) != 0) &&
2350 (strcmpW(fd.cFileName, dotW) != 0))
2352 thisCmdStart = cmdStart;
2353 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2355 if (doRecurse) {
2356 strcpyW(fullitem, dirsToWalk->dirName);
2357 strcatW(fullitem, slashW);
2358 strcatW(fullitem, fd.cFileName);
2359 } else {
2360 if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
2361 fullitem[prefixlen] = 0x00;
2362 strcatW(fullitem, fd.cFileName);
2364 doExecuted = TRUE;
2366 /* Save away any existing for variable context (e.g. nested for loops)
2367 and restore it after executing the body of this for loop */
2368 if (varidx >= 0) {
2369 oldvariablevalue = forloopcontext.variable[varidx];
2370 forloopcontext.variable[varidx] = fullitem;
2372 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2373 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2375 cmdEnd = thisCmdStart;
2377 } while (FindNextFileW(hff, &fd) != 0);
2378 FindClose (hff);
2380 } else {
2381 doExecuted = TRUE;
2383 /* Save away any existing for variable context (e.g. nested for loops)
2384 and restore it after executing the body of this for loop */
2385 if (varidx >= 0) {
2386 oldvariablevalue = forloopcontext.variable[varidx];
2387 forloopcontext.variable[varidx] = fullitem;
2389 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2390 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2392 cmdEnd = thisCmdStart;
2395 } else if (useNumbers) {
2396 /* Convert the first 3 numbers to signed longs and save */
2397 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
2398 /* else ignore them! */
2400 /* Filesets - either a list of files, or a command to run and parse the output */
2401 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2402 (forf_usebackq && *itemStart != '\''))) {
2404 HANDLE input;
2405 WCHAR *itemparm;
2407 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2408 wine_dbgstr_w(item));
2410 /* If backquote or single quote, we need to launch that command
2411 and parse the results - use a temporary file */
2412 if ((forf_usebackq && *itemStart == '`') ||
2413 (!forf_usebackq && *itemStart == '\'')) {
2415 /* Use itemstart because the command is the whole set, not just the first token */
2416 itemparm = itemStart;
2417 } else {
2419 /* Use item because the file to process is just the first item in the set */
2420 itemparm = item;
2422 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2424 /* Process the input file */
2425 if (input == INVALID_HANDLE_VALUE) {
2426 WCMD_print_error ();
2427 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2428 errorlevel = 1;
2429 return; /* FOR loop aborts at first failure here */
2431 } else {
2433 /* Read line by line until end of file */
2434 while (WCMD_fgets(buffer, ARRAY_SIZE(buffer), input)) {
2435 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2436 &forf_skip, forf_eol, forf_delims, forf_tokens);
2437 buffer[0] = 0;
2439 CloseHandle (input);
2442 /* When we have processed the item as a whole command, abort future set processing */
2443 if (itemparm==itemStart) {
2444 thisSet = NULL;
2445 break;
2448 /* Filesets - A string literal */
2449 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2450 (forf_usebackq && *itemStart == '\''))) {
2452 /* Remove leading and trailing character, ready to parse with delims= delimiters
2453 Note that the last quote is removed from the set and the string terminates
2454 there to mimic windows */
2455 WCHAR *strend = strrchrW(itemStart, forf_usebackq?'\'':'"');
2456 if (strend) {
2457 *strend = 0x00;
2458 itemStart++;
2461 /* Copy the item away from the global buffer used by WCMD_parameter */
2462 strcpyW(buffer, itemStart);
2463 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2464 &forf_skip, forf_eol, forf_delims, forf_tokens);
2466 /* Only one string can be supplied in the whole set, abort future set processing */
2467 thisSet = NULL;
2468 break;
2471 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2472 i++;
2475 /* Move onto the next set line */
2476 if (thisSet) thisSet = thisSet->nextcommand;
2479 /* If /L is provided, now run the for loop */
2480 if (useNumbers) {
2481 WCHAR thisNum[20];
2482 static const WCHAR fmt[] = {'%','d','\0'};
2484 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2485 numbers[0], numbers[2], numbers[1]);
2486 for (i=numbers[0];
2487 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2488 i=i + numbers[1]) {
2490 sprintfW(thisNum, fmt, i);
2491 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2493 thisCmdStart = cmdStart;
2494 doExecuted = TRUE;
2496 /* Save away any existing for variable context (e.g. nested for loops)
2497 and restore it after executing the body of this for loop */
2498 if (varidx >= 0) {
2499 oldvariablevalue = forloopcontext.variable[varidx];
2500 forloopcontext.variable[varidx] = thisNum;
2502 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2503 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2505 cmdEnd = thisCmdStart;
2508 /* If we are walking directories, move on to any which remain */
2509 if (dirsToWalk != NULL) {
2510 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2511 heap_free(dirsToWalk->dirName);
2512 heap_free(dirsToWalk);
2513 dirsToWalk = nextDir;
2514 if (dirsToWalk) WINE_TRACE("Moving to next directorty to iterate: %s\n",
2515 wine_dbgstr_w(dirsToWalk->dirName));
2516 else WINE_TRACE("Finished all directories.\n");
2519 } while (dirsToWalk != NULL);
2521 /* Now skip over the do part if we did not perform the for loop so far.
2522 We store in cmdEnd the next command after the do block, but we only
2523 know this if something was run. If it has not been, we need to calculate
2524 it. */
2525 if (!doExecuted) {
2526 thisCmdStart = cmdStart;
2527 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2528 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2529 cmdEnd = thisCmdStart;
2532 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2533 all processing, OR it should be pointing to the end of && processing OR
2534 it should be pointing at the NULL end of bracket for the DO. The return
2535 value needs to be the NEXT command to execute, which it either is, or
2536 we need to step over the closing bracket */
2537 *cmdList = cmdEnd;
2538 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2541 /**************************************************************************
2542 * WCMD_give_help
2544 * Simple on-line help. Help text is stored in the resource file.
2547 void WCMD_give_help (const WCHAR *args)
2549 size_t i;
2551 args = WCMD_skip_leading_spaces((WCHAR*) args);
2552 if (strlenW(args) == 0) {
2553 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2555 else {
2556 /* Display help message for builtin commands */
2557 for (i=0; i<=WCMD_EXIT; i++) {
2558 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2559 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2560 WCMD_output_asis (WCMD_LoadMessage(i));
2561 return;
2564 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2565 for (i = 0; i <= ARRAY_SIZE(externals); i++) {
2566 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2567 args, -1, externals[i], -1) == CSTR_EQUAL) {
2568 WCHAR cmd[128];
2569 static const WCHAR helpW[] = {' ', '/','?','\0'};
2570 strcpyW(cmd, args);
2571 strcatW(cmd, helpW);
2572 WCMD_run_program(cmd, FALSE);
2573 return;
2576 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2578 return;
2581 /****************************************************************************
2582 * WCMD_go_to
2584 * Batch file jump instruction. Not the most efficient algorithm ;-)
2585 * Prints error message if the specified label cannot be found - the file pointer is
2586 * then at EOF, effectively stopping the batch file.
2587 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2590 void WCMD_goto (CMD_LIST **cmdList) {
2592 WCHAR string[MAX_PATH];
2593 WCHAR *labelend = NULL;
2594 const WCHAR labelEndsW[] = {'>','<','|','&',' ',':','\t','\0'};
2596 /* Do not process any more parts of a processed multipart or multilines command */
2597 if (cmdList) *cmdList = NULL;
2599 if (context != NULL) {
2600 WCHAR *paramStart = param1, *str;
2601 static const WCHAR eofW[] = {':','e','o','f','\0'};
2603 if (param1[0] == 0x00) {
2604 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2605 return;
2608 /* Handle special :EOF label */
2609 if (lstrcmpiW (eofW, param1) == 0) {
2610 context -> skip_rest = TRUE;
2611 return;
2614 /* Support goto :label as well as goto label plus remove trailing chars */
2615 if (*paramStart == ':') paramStart++;
2616 labelend = strpbrkW(paramStart, labelEndsW);
2617 if (labelend) *labelend = 0x00;
2618 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
2620 /* Loop through potentially twice - once from current file position
2621 through to the end, and second time from start to current file
2622 position */
2623 if (*paramStart) {
2624 int loop;
2625 LARGE_INTEGER startli;
2626 for (loop=0; loop<2; loop++) {
2627 if (loop==0) {
2628 /* On first loop, save the file size */
2629 startli.QuadPart = 0;
2630 startli.u.LowPart = SetFilePointer(context -> h, startli.u.LowPart,
2631 &startli.u.HighPart, FILE_CURRENT);
2632 } else {
2633 /* On second loop, start at the beginning of the file */
2634 WINE_TRACE("Label not found, trying from beginning of file\n");
2635 if (loop==1) SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2638 while (WCMD_fgets (string, ARRAY_SIZE(string), context -> h)) {
2639 str = string;
2641 /* Ignore leading whitespace or no-echo character */
2642 while (*str=='@' || isspaceW (*str)) str++;
2644 /* If the first real character is a : then this is a label */
2645 if (*str == ':') {
2646 str++;
2648 /* Skip spaces between : and label */
2649 while (isspaceW (*str)) str++;
2650 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
2652 /* Label ends at whitespace or redirection characters */
2653 labelend = strpbrkW(str, labelEndsW);
2654 if (labelend) *labelend = 0x00;
2655 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
2657 if (lstrcmpiW (str, paramStart) == 0) return;
2660 /* See if we have gone beyond the end point if second time through */
2661 if (loop==1) {
2662 LARGE_INTEGER curli;
2663 curli.QuadPart = 0;
2664 curli.u.LowPart = SetFilePointer(context -> h, curli.u.LowPart,
2665 &curli.u.HighPart, FILE_CURRENT);
2666 if (curli.QuadPart > startli.QuadPart) {
2667 WINE_TRACE("Reached wrap point, label not found\n");
2668 break;
2675 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2676 context -> skip_rest = TRUE;
2678 return;
2681 /*****************************************************************************
2682 * WCMD_pushd
2684 * Push a directory onto the stack
2687 void WCMD_pushd (const WCHAR *args)
2689 struct env_stack *curdir;
2690 WCHAR *thisdir;
2691 static const WCHAR parmD[] = {'/','D','\0'};
2693 if (strchrW(args, '/') != NULL) {
2694 SetLastError(ERROR_INVALID_PARAMETER);
2695 WCMD_print_error();
2696 return;
2699 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2700 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2701 if( !curdir || !thisdir ) {
2702 LocalFree(curdir);
2703 LocalFree(thisdir);
2704 WINE_ERR ("out of memory\n");
2705 return;
2708 /* Change directory using CD code with /D parameter */
2709 strcpyW(quals, parmD);
2710 GetCurrentDirectoryW (1024, thisdir);
2711 errorlevel = 0;
2712 WCMD_setshow_default(args);
2713 if (errorlevel) {
2714 LocalFree(curdir);
2715 LocalFree(thisdir);
2716 return;
2717 } else {
2718 curdir -> next = pushd_directories;
2719 curdir -> strings = thisdir;
2720 if (pushd_directories == NULL) {
2721 curdir -> u.stackdepth = 1;
2722 } else {
2723 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2725 pushd_directories = curdir;
2730 /*****************************************************************************
2731 * WCMD_popd
2733 * Pop a directory from the stack
2736 void WCMD_popd (void) {
2737 struct env_stack *temp = pushd_directories;
2739 if (!pushd_directories)
2740 return;
2742 /* pop the old environment from the stack, and make it the current dir */
2743 pushd_directories = temp->next;
2744 SetCurrentDirectoryW(temp->strings);
2745 LocalFree (temp->strings);
2746 LocalFree (temp);
2749 /*******************************************************************
2750 * evaluate_if_comparison
2752 * Evaluates an "if" comparison operation
2754 * PARAMS
2755 * leftOperand [I] left operand, non NULL
2756 * operator [I] "if" binary comparison operator, non NULL
2757 * rightOperand [I] right operand, non NULL
2758 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2760 * RETURNS
2761 * Success: 1 if operator applied to the operands evaluates to TRUE
2762 * 0 if operator applied to the operands evaluates to FALSE
2763 * Failure: -1 if operator is not recognized
2765 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2766 const WCHAR *rightOperand, int caseInsensitive)
2768 WCHAR *endptr_leftOp, *endptr_rightOp;
2769 long int leftOperand_int, rightOperand_int;
2770 BOOL int_operands;
2771 static const WCHAR lssW[] = {'l','s','s','\0'};
2772 static const WCHAR leqW[] = {'l','e','q','\0'};
2773 static const WCHAR equW[] = {'e','q','u','\0'};
2774 static const WCHAR neqW[] = {'n','e','q','\0'};
2775 static const WCHAR geqW[] = {'g','e','q','\0'};
2776 static const WCHAR gtrW[] = {'g','t','r','\0'};
2778 /* == is a special case, as it always compares strings */
2779 if (!lstrcmpiW(operator, eqeqW))
2780 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2781 : lstrcmpW (leftOperand, rightOperand) == 0;
2783 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2784 leftOperand_int = strtolW(leftOperand, &endptr_leftOp, 0);
2785 rightOperand_int = strtolW(rightOperand, &endptr_rightOp, 0);
2786 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2788 /* Perform actual (integer or string) comparison */
2789 if (!lstrcmpiW(operator, lssW)) {
2790 if (int_operands)
2791 return leftOperand_int < rightOperand_int;
2792 else
2793 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2794 : lstrcmpW (leftOperand, rightOperand) < 0;
2797 if (!lstrcmpiW(operator, leqW)) {
2798 if (int_operands)
2799 return leftOperand_int <= rightOperand_int;
2800 else
2801 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2802 : lstrcmpW (leftOperand, rightOperand) <= 0;
2805 if (!lstrcmpiW(operator, equW)) {
2806 if (int_operands)
2807 return leftOperand_int == rightOperand_int;
2808 else
2809 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2810 : lstrcmpW (leftOperand, rightOperand) == 0;
2813 if (!lstrcmpiW(operator, neqW)) {
2814 if (int_operands)
2815 return leftOperand_int != rightOperand_int;
2816 else
2817 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2818 : lstrcmpW (leftOperand, rightOperand) != 0;
2821 if (!lstrcmpiW(operator, geqW)) {
2822 if (int_operands)
2823 return leftOperand_int >= rightOperand_int;
2824 else
2825 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2826 : lstrcmpW (leftOperand, rightOperand) >= 0;
2829 if (!lstrcmpiW(operator, gtrW)) {
2830 if (int_operands)
2831 return leftOperand_int > rightOperand_int;
2832 else
2833 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2834 : lstrcmpW (leftOperand, rightOperand) > 0;
2837 return -1;
2840 /****************************************************************************
2841 * WCMD_if
2843 * Batch file conditional.
2845 * On entry, cmdlist will point to command containing the IF, and optionally
2846 * the first command to execute (if brackets not found)
2847 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2848 * If ('s were found, execute all within that bracket
2849 * Command may optionally be followed by an ELSE - need to skip instructions
2850 * in the else using the same logic
2852 * FIXME: Much more syntax checking needed!
2854 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2856 int negate; /* Negate condition */
2857 int test; /* Condition evaluation result */
2858 WCHAR condition[MAX_PATH], *command;
2859 static const WCHAR notW[] = {'n','o','t','\0'};
2860 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
2861 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
2862 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
2863 static const WCHAR parmI[] = {'/','I','\0'};
2864 int caseInsensitive = (strstrW(quals, parmI) != NULL);
2866 negate = !lstrcmpiW(param1,notW);
2867 strcpyW(condition, (negate ? param2 : param1));
2868 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2870 if (!lstrcmpiW (condition, errlvlW)) {
2871 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2872 WCHAR *endptr;
2873 long int param_int = strtolW(param, &endptr, 10);
2874 if (*endptr) goto syntax_err;
2875 test = ((long int)errorlevel >= param_int);
2876 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2878 else if (!lstrcmpiW (condition, existW)) {
2879 WIN32_FIND_DATAW fd;
2880 HANDLE hff;
2881 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE);
2882 int len = strlenW(param);
2884 /* FindFirstFile does not like a directory path ending in '\', append a '.' */
2885 if (len && param[len-1] == '\\') strcatW(param, dotW);
2887 hff = FindFirstFileW(param, &fd);
2888 test = (hff != INVALID_HANDLE_VALUE );
2889 if (test) FindClose(hff);
2891 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2893 else if (!lstrcmpiW (condition, defdW)) {
2894 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, FALSE, FALSE),
2895 NULL, 0) > 0);
2896 WCMD_parameter(p, 2+negate, &command, FALSE, FALSE);
2898 else { /* comparison operation */
2899 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2900 WCHAR *paramStart;
2902 strcpyW(leftOperand, WCMD_parameter(p, negate+caseInsensitive, &paramStart, TRUE, FALSE));
2903 if (!*leftOperand)
2904 goto syntax_err;
2906 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2907 p = paramStart + strlenW(leftOperand);
2908 while (*p == ' ' || *p == '\t')
2909 p++;
2911 if (!strncmpW(p, eqeqW, strlenW(eqeqW)))
2912 strcpyW(operator, eqeqW);
2913 else {
2914 strcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2915 if (!*operator) goto syntax_err;
2917 p += strlenW(operator);
2919 strcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2920 if (!*rightOperand)
2921 goto syntax_err;
2923 test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2924 if (test == -1)
2925 goto syntax_err;
2927 p = paramStart + strlenW(rightOperand);
2928 WCMD_parameter(p, 0, &command, FALSE, FALSE);
2931 /* Process rest of IF statement which is on the same line
2932 Note: This may process all or some of the cmdList (eg a GOTO) */
2933 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2934 return;
2936 syntax_err:
2937 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2940 /****************************************************************************
2941 * WCMD_move
2943 * Move a file, directory tree or wildcarded set of files.
2946 void WCMD_move (void)
2948 BOOL status;
2949 WIN32_FIND_DATAW fd;
2950 HANDLE hff;
2951 WCHAR input[MAX_PATH];
2952 WCHAR output[MAX_PATH];
2953 WCHAR drive[10];
2954 WCHAR dir[MAX_PATH];
2955 WCHAR fname[MAX_PATH];
2956 WCHAR ext[MAX_PATH];
2958 if (param1[0] == 0x00) {
2959 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2960 return;
2963 /* If no destination supplied, assume current directory */
2964 if (param2[0] == 0x00) {
2965 strcpyW(param2, dotW);
2968 /* If 2nd parm is directory, then use original filename */
2969 /* Convert partial path to full path */
2970 GetFullPathNameW(param1, ARRAY_SIZE(input), input, NULL);
2971 GetFullPathNameW(param2, ARRAY_SIZE(output), output, NULL);
2972 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2973 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2975 /* Split into components */
2976 WCMD_splitpath(input, drive, dir, fname, ext);
2978 hff = FindFirstFileW(input, &fd);
2979 if (hff == INVALID_HANDLE_VALUE)
2980 return;
2982 do {
2983 WCHAR dest[MAX_PATH];
2984 WCHAR src[MAX_PATH];
2985 DWORD attribs;
2986 BOOL ok = TRUE;
2988 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2990 /* Build src & dest name */
2991 strcpyW(src, drive);
2992 strcatW(src, dir);
2994 /* See if dest is an existing directory */
2995 attribs = GetFileAttributesW(output);
2996 if (attribs != INVALID_FILE_ATTRIBUTES &&
2997 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2998 strcpyW(dest, output);
2999 strcatW(dest, slashW);
3000 strcatW(dest, fd.cFileName);
3001 } else {
3002 strcpyW(dest, output);
3005 strcatW(src, fd.cFileName);
3007 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3008 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3010 /* If destination exists, prompt unless /Y supplied */
3011 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
3012 BOOL force = FALSE;
3013 WCHAR copycmd[MAXSTRING];
3014 DWORD len;
3016 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
3017 if (strstrW (quals, parmNoY))
3018 force = FALSE;
3019 else if (strstrW (quals, parmY))
3020 force = TRUE;
3021 else {
3022 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
3023 len = GetEnvironmentVariableW(copyCmdW, copycmd, ARRAY_SIZE(copycmd));
3024 force = (len && len < ARRAY_SIZE(copycmd) && !lstrcmpiW(copycmd, parmY));
3027 /* Prompt if overwriting */
3028 if (!force) {
3029 WCHAR* question;
3031 /* Ask for confirmation */
3032 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
3033 ok = WCMD_ask_confirm(question, FALSE, NULL);
3034 LocalFree(question);
3036 /* So delete the destination prior to the move */
3037 if (ok) {
3038 if (!DeleteFileW(dest)) {
3039 WCMD_print_error ();
3040 errorlevel = 1;
3041 ok = FALSE;
3047 if (ok) {
3048 status = MoveFileW(src, dest);
3049 } else {
3050 status = TRUE;
3053 if (!status) {
3054 WCMD_print_error ();
3055 errorlevel = 1;
3057 } while (FindNextFileW(hff, &fd) != 0);
3059 FindClose(hff);
3062 /****************************************************************************
3063 * WCMD_pause
3065 * Suspend execution of a batch script until a key is typed
3068 void WCMD_pause (void)
3070 DWORD oldmode;
3071 BOOL have_console;
3072 DWORD count;
3073 WCHAR key;
3074 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
3076 have_console = GetConsoleMode(hIn, &oldmode);
3077 if (have_console)
3078 SetConsoleMode(hIn, 0);
3080 WCMD_output_asis(anykey);
3081 WCMD_ReadFile(hIn, &key, 1, &count);
3082 if (have_console)
3083 SetConsoleMode(hIn, oldmode);
3086 /****************************************************************************
3087 * WCMD_remove_dir
3089 * Delete a directory.
3092 void WCMD_remove_dir (WCHAR *args) {
3094 int argno = 0;
3095 int argsProcessed = 0;
3096 WCHAR *argN = args;
3097 static const WCHAR parmS[] = {'/','S','\0'};
3098 static const WCHAR parmQ[] = {'/','Q','\0'};
3100 /* Loop through all args */
3101 while (argN) {
3102 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3103 if (argN && argN[0] != '/') {
3104 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
3105 wine_dbgstr_w(quals));
3106 argsProcessed++;
3108 /* If subdirectory search not supplied, just try to remove
3109 and report error if it fails (eg if it contains a file) */
3110 if (strstrW (quals, parmS) == NULL) {
3111 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
3113 /* Otherwise use ShFileOp to recursively remove a directory */
3114 } else {
3116 SHFILEOPSTRUCTW lpDir;
3118 /* Ask first */
3119 if (strstrW (quals, parmQ) == NULL) {
3120 BOOL ok;
3121 WCHAR question[MAXSTRING];
3122 static const WCHAR fmt[] = {'%','s',' ','\0'};
3124 /* Ask for confirmation */
3125 wsprintfW(question, fmt, thisArg);
3126 ok = WCMD_ask_confirm(question, TRUE, NULL);
3128 /* Abort if answer is 'N' */
3129 if (!ok) return;
3132 /* Do the delete */
3133 lpDir.hwnd = NULL;
3134 lpDir.pTo = NULL;
3135 lpDir.pFrom = thisArg;
3136 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
3137 lpDir.wFunc = FO_DELETE;
3139 /* SHFileOperationW needs file list with a double null termination */
3140 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3142 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3147 /* Handle no valid args */
3148 if (argsProcessed == 0) {
3149 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3150 return;
3155 /****************************************************************************
3156 * WCMD_rename
3158 * Rename a file.
3161 void WCMD_rename (void)
3163 BOOL status;
3164 HANDLE hff;
3165 WIN32_FIND_DATAW fd;
3166 WCHAR input[MAX_PATH];
3167 WCHAR *dotDst = NULL;
3168 WCHAR drive[10];
3169 WCHAR dir[MAX_PATH];
3170 WCHAR fname[MAX_PATH];
3171 WCHAR ext[MAX_PATH];
3173 errorlevel = 0;
3175 /* Must be at least two args */
3176 if (param1[0] == 0x00 || param2[0] == 0x00) {
3177 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3178 errorlevel = 1;
3179 return;
3182 /* Destination cannot contain a drive letter or directory separator */
3183 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
3184 SetLastError(ERROR_INVALID_PARAMETER);
3185 WCMD_print_error();
3186 errorlevel = 1;
3187 return;
3190 /* Convert partial path to full path */
3191 GetFullPathNameW(param1, ARRAY_SIZE(input), input, NULL);
3192 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3193 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3194 dotDst = strchrW(param2, '.');
3196 /* Split into components */
3197 WCMD_splitpath(input, drive, dir, fname, ext);
3199 hff = FindFirstFileW(input, &fd);
3200 if (hff == INVALID_HANDLE_VALUE)
3201 return;
3203 do {
3204 WCHAR dest[MAX_PATH];
3205 WCHAR src[MAX_PATH];
3206 WCHAR *dotSrc = NULL;
3207 int dirLen;
3209 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3211 /* FIXME: If dest name or extension is *, replace with filename/ext
3212 part otherwise use supplied name. This supports:
3213 ren *.fred *.jim
3214 ren jim.* fred.* etc
3215 However, windows has a more complex algorithm supporting eg
3216 ?'s and *'s mid name */
3217 dotSrc = strchrW(fd.cFileName, '.');
3219 /* Build src & dest name */
3220 strcpyW(src, drive);
3221 strcatW(src, dir);
3222 strcpyW(dest, src);
3223 dirLen = strlenW(src);
3224 strcatW(src, fd.cFileName);
3226 /* Build name */
3227 if (param2[0] == '*') {
3228 strcatW(dest, fd.cFileName);
3229 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3230 } else {
3231 strcatW(dest, param2);
3232 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3235 /* Build Extension */
3236 if (dotDst && (*(dotDst+1)=='*')) {
3237 if (dotSrc) strcatW(dest, dotSrc);
3238 } else if (dotDst) {
3239 strcatW(dest, dotDst);
3242 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3243 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3245 status = MoveFileW(src, dest);
3247 if (!status) {
3248 WCMD_print_error ();
3249 errorlevel = 1;
3251 } while (FindNextFileW(hff, &fd) != 0);
3253 FindClose(hff);
3256 /*****************************************************************************
3257 * WCMD_dupenv
3259 * Make a copy of the environment.
3261 static WCHAR *WCMD_dupenv( const WCHAR *env )
3263 WCHAR *env_copy;
3264 int len;
3266 if( !env )
3267 return NULL;
3269 len = 0;
3270 while ( env[len] )
3271 len += (strlenW(&env[len]) + 1);
3273 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3274 if (!env_copy)
3276 WINE_ERR("out of memory\n");
3277 return env_copy;
3279 memcpy (env_copy, env, len*sizeof (WCHAR));
3280 env_copy[len] = 0;
3282 return env_copy;
3285 /*****************************************************************************
3286 * WCMD_setlocal
3288 * setlocal pushes the environment onto a stack
3289 * Save the environment as unicode so we don't screw anything up.
3291 void WCMD_setlocal (const WCHAR *s) {
3292 WCHAR *env;
3293 struct env_stack *env_copy;
3294 WCHAR cwd[MAX_PATH];
3295 BOOL newdelay;
3296 static const WCHAR ondelayW[] = {'E','N','A','B','L','E','D','E','L','A',
3297 'Y','E','D','E','X','P','A','N','S','I',
3298 'O','N','\0'};
3299 static const WCHAR offdelayW[] = {'D','I','S','A','B','L','E','D','E','L',
3300 'A','Y','E','D','E','X','P','A','N','S',
3301 'I','O','N','\0'};
3303 /* setlocal does nothing outside of batch programs */
3304 if (!context) return;
3306 /* DISABLEEXTENSIONS ignored */
3308 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3309 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3310 if (!strcmpiW(param1, ondelayW) || !strcmpiW(param2, ondelayW)) {
3311 newdelay = TRUE;
3312 } else if (!strcmpiW(param1, offdelayW) || !strcmpiW(param2, offdelayW)) {
3313 newdelay = FALSE;
3314 } else {
3315 newdelay = delayedsubst;
3317 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3319 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3320 if( !env_copy )
3322 WINE_ERR ("out of memory\n");
3323 return;
3326 env = GetEnvironmentStringsW ();
3327 env_copy->strings = WCMD_dupenv (env);
3328 if (env_copy->strings)
3330 env_copy->batchhandle = context->h;
3331 env_copy->next = saved_environment;
3332 env_copy->delayedsubst = delayedsubst;
3333 delayedsubst = newdelay;
3334 saved_environment = env_copy;
3336 /* Save the current drive letter */
3337 GetCurrentDirectoryW(MAX_PATH, cwd);
3338 env_copy->u.cwd = cwd[0];
3340 else
3341 LocalFree (env_copy);
3343 FreeEnvironmentStringsW (env);
3347 /*****************************************************************************
3348 * WCMD_endlocal
3350 * endlocal pops the environment off a stack
3351 * Note: When searching for '=', search from WCHAR position 1, to handle
3352 * special internal environment variables =C:, =D: etc
3354 void WCMD_endlocal (void) {
3355 WCHAR *env, *old, *p;
3356 struct env_stack *temp;
3357 int len, n;
3359 /* setlocal does nothing outside of batch programs */
3360 if (!context) return;
3362 /* setlocal needs a saved environment from within the same context (batch
3363 program) as it was saved in */
3364 if (!saved_environment || saved_environment->batchhandle != context->h)
3365 return;
3367 /* pop the old environment from the stack */
3368 temp = saved_environment;
3369 saved_environment = temp->next;
3371 /* delete the current environment, totally */
3372 env = GetEnvironmentStringsW ();
3373 old = WCMD_dupenv (env);
3374 len = 0;
3375 while (old[len]) {
3376 n = strlenW(&old[len]) + 1;
3377 p = strchrW(&old[len] + 1, '=');
3378 if (p)
3380 *p++ = 0;
3381 SetEnvironmentVariableW (&old[len], NULL);
3383 len += n;
3385 LocalFree (old);
3386 FreeEnvironmentStringsW (env);
3388 /* restore old environment */
3389 env = temp->strings;
3390 len = 0;
3391 delayedsubst = temp->delayedsubst;
3392 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3393 while (env[len]) {
3394 n = strlenW(&env[len]) + 1;
3395 p = strchrW(&env[len] + 1, '=');
3396 if (p)
3398 *p++ = 0;
3399 SetEnvironmentVariableW (&env[len], p);
3401 len += n;
3404 /* Restore current drive letter */
3405 if (IsCharAlphaW(temp->u.cwd)) {
3406 WCHAR envvar[4];
3407 WCHAR cwd[MAX_PATH];
3408 static const WCHAR fmt[] = {'=','%','c',':','\0'};
3410 wsprintfW(envvar, fmt, temp->u.cwd);
3411 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3412 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3413 SetCurrentDirectoryW(cwd);
3417 LocalFree (env);
3418 LocalFree (temp);
3421 /*****************************************************************************
3422 * WCMD_setshow_default
3424 * Set/Show the current default directory
3427 void WCMD_setshow_default (const WCHAR *args) {
3429 BOOL status;
3430 WCHAR string[1024];
3431 WCHAR cwd[1024];
3432 WCHAR *pos;
3433 WIN32_FIND_DATAW fd;
3434 HANDLE hff;
3435 static const WCHAR parmD[] = {'/','D','\0'};
3437 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3439 /* Skip /D and trailing whitespace if on the front of the command line */
3440 if (strlenW(args) >= 2 &&
3441 CompareStringW(LOCALE_USER_DEFAULT,
3442 NORM_IGNORECASE | SORT_STRINGSORT,
3443 args, 2, parmD, -1) == CSTR_EQUAL) {
3444 args += 2;
3445 while (*args && (*args==' ' || *args=='\t'))
3446 args++;
3449 GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
3450 if (strlenW(args) == 0) {
3451 strcatW (cwd, newlineW);
3452 WCMD_output_asis (cwd);
3454 else {
3455 /* Remove any double quotes, which may be in the
3456 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3457 pos = string;
3458 while (*args) {
3459 if (*args != '"') *pos++ = *args;
3460 args++;
3462 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3463 pos--;
3464 *pos = 0x00;
3466 /* Search for appropriate directory */
3467 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3468 hff = FindFirstFileW(string, &fd);
3469 if (hff != INVALID_HANDLE_VALUE) {
3470 do {
3471 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3472 WCHAR fpath[MAX_PATH];
3473 WCHAR drive[10];
3474 WCHAR dir[MAX_PATH];
3475 WCHAR fname[MAX_PATH];
3476 WCHAR ext[MAX_PATH];
3477 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
3479 /* Convert path into actual directory spec */
3480 GetFullPathNameW(string, ARRAY_SIZE(fpath), fpath, NULL);
3481 WCMD_splitpath(fpath, drive, dir, fname, ext);
3483 /* Rebuild path */
3484 wsprintfW(string, fmt, drive, dir, fd.cFileName);
3485 break;
3487 } while (FindNextFileW(hff, &fd) != 0);
3488 FindClose(hff);
3491 /* Change to that directory */
3492 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3494 status = SetCurrentDirectoryW(string);
3495 if (!status) {
3496 errorlevel = 1;
3497 WCMD_print_error ();
3498 return;
3499 } else {
3501 /* Save away the actual new directory, to store as current location */
3502 GetCurrentDirectoryW(ARRAY_SIZE(string), string);
3504 /* Restore old directory if drive letter would change, and
3505 CD x:\directory /D (or pushd c:\directory) not supplied */
3506 if ((strstrW(quals, parmD) == NULL) &&
3507 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3508 SetCurrentDirectoryW(cwd);
3512 /* Set special =C: type environment variable, for drive letter of
3513 change of directory, even if path was restored due to missing
3514 /D (allows changing drive letter when not resident on that
3515 drive */
3516 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3517 WCHAR env[4];
3518 strcpyW(env, equalW);
3519 memcpy(env+1, string, 2 * sizeof(WCHAR));
3520 env[3] = 0x00;
3521 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3522 SetEnvironmentVariableW(env, string);
3526 return;
3529 /****************************************************************************
3530 * WCMD_setshow_date
3532 * Set/Show the system date
3533 * FIXME: Can't change date yet
3536 void WCMD_setshow_date (void) {
3538 WCHAR curdate[64], buffer[64];
3539 DWORD count;
3540 static const WCHAR parmT[] = {'/','T','\0'};
3542 if (strlenW(param1) == 0) {
3543 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, curdate, ARRAY_SIZE(curdate))) {
3544 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3545 if (strstrW (quals, parmT) == NULL) {
3546 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3547 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
3548 if (count > 2) {
3549 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3553 else WCMD_print_error ();
3555 else {
3556 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3560 /****************************************************************************
3561 * WCMD_compare
3562 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3563 * the equals sign.
3565 static int WCMD_compare( const void *a, const void *b )
3567 int r;
3568 const WCHAR * const *str_a = a, * const *str_b = b;
3569 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3570 *str_a, strcspnW(*str_a, equalW), *str_b, strcspnW(*str_b, equalW) );
3571 if( r == CSTR_LESS_THAN ) return -1;
3572 if( r == CSTR_GREATER_THAN ) return 1;
3573 return 0;
3576 /****************************************************************************
3577 * WCMD_setshow_sortenv
3579 * sort variables into order for display
3580 * Optionally only display those who start with a stub
3581 * returns the count displayed
3583 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3585 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3586 const WCHAR **str;
3588 if (stub) stublen = strlenW(stub);
3590 /* count the number of strings, and the total length */
3591 while ( s[len] ) {
3592 len += (strlenW(&s[len]) + 1);
3593 count++;
3596 /* add the strings to an array */
3597 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3598 if( !str )
3599 return 0;
3600 str[0] = s;
3601 for( i=1; i<count; i++ )
3602 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
3604 /* sort the array */
3605 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3607 /* print it */
3608 for( i=0; i<count; i++ ) {
3609 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3610 NORM_IGNORECASE | SORT_STRINGSORT,
3611 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3612 /* Don't display special internal variables */
3613 if (str[i][0] != '=') {
3614 WCMD_output_asis(str[i]);
3615 WCMD_output_asis(newlineW);
3616 displayedcount++;
3621 LocalFree( str );
3622 return displayedcount;
3625 /****************************************************************************
3626 * WCMD_getprecedence
3627 * Return the precedence of a particular operator
3629 static int WCMD_getprecedence(const WCHAR in)
3631 switch (in) {
3632 case '!':
3633 case '~':
3634 case OP_POSITIVE:
3635 case OP_NEGATIVE:
3636 return 8;
3637 case '*':
3638 case '/':
3639 case '%':
3640 return 7;
3641 case '+':
3642 case '-':
3643 return 6;
3644 case '<':
3645 case '>':
3646 return 5;
3647 case '&':
3648 return 4;
3649 case '^':
3650 return 3;
3651 case '|':
3652 return 2;
3653 case '=':
3654 case OP_ASSSIGNMUL:
3655 case OP_ASSSIGNDIV:
3656 case OP_ASSSIGNMOD:
3657 case OP_ASSSIGNADD:
3658 case OP_ASSSIGNSUB:
3659 case OP_ASSSIGNAND:
3660 case OP_ASSSIGNNOT:
3661 case OP_ASSSIGNOR:
3662 case OP_ASSSIGNSHL:
3663 case OP_ASSSIGNSHR:
3664 return 1;
3665 default:
3666 return 0;
3670 /****************************************************************************
3671 * WCMD_pushnumber
3672 * Push either a number or name (environment variable) onto the supplied
3673 * stack
3675 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3676 VARSTACK *thisstack = heap_alloc(sizeof(VARSTACK));
3677 thisstack->isnum = (var == NULL);
3678 if (var) {
3679 thisstack->variable = var;
3680 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3681 } else {
3682 thisstack->value = num;
3683 WINE_TRACE("Pushed number %d\n", num);
3685 thisstack->next = *varstack;
3686 *varstack = thisstack;
3689 /****************************************************************************
3690 * WCMD_peeknumber
3691 * Returns the value of the top number or environment variable on the stack
3692 * and leaves the item on the stack.
3694 static int WCMD_peeknumber(VARSTACK **varstack) {
3695 int result = 0;
3696 VARSTACK *thisvar;
3698 if (varstack) {
3699 thisvar = *varstack;
3700 if (!thisvar->isnum) {
3701 WCHAR tmpstr[MAXSTRING];
3702 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3703 result = strtoulW(tmpstr,NULL,0);
3705 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3706 } else {
3707 result = thisvar->value;
3710 WINE_TRACE("Peeked number %d\n", result);
3711 return result;
3714 /****************************************************************************
3715 * WCMD_popnumber
3716 * Returns the value of the top number or environment variable on the stack
3717 * and removes the item from the stack.
3719 static int WCMD_popnumber(VARSTACK **varstack) {
3720 int result = 0;
3721 VARSTACK *thisvar;
3723 if (varstack) {
3724 thisvar = *varstack;
3725 result = WCMD_peeknumber(varstack);
3726 if (!thisvar->isnum) heap_free(thisvar->variable);
3727 *varstack = thisvar->next;
3728 heap_free(thisvar);
3730 WINE_TRACE("Popped number %d\n", result);
3731 return result;
3734 /****************************************************************************
3735 * WCMD_pushoperator
3736 * Push an operator onto the supplied stack
3738 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3739 OPSTACK *thisstack = heap_alloc(sizeof(OPSTACK));
3740 thisstack->precedence = precedence;
3741 thisstack->op = op;
3742 thisstack->next = *opstack;
3743 WINE_TRACE("Pushed operator %c\n", op);
3744 *opstack = thisstack;
3747 /****************************************************************************
3748 * WCMD_popoperator
3749 * Returns the operator from the top of the stack and removes the item from
3750 * the stack.
3752 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3753 WCHAR result = 0;
3754 OPSTACK *thisop;
3756 if (opstack) {
3757 thisop = *opstack;
3758 result = thisop->op;
3759 *opstack = thisop->next;
3760 heap_free(thisop);
3762 WINE_TRACE("Popped operator %c\n", result);
3763 return result;
3766 /****************************************************************************
3767 * WCMD_reduce
3768 * Actions the top operator on the stack against the first and sometimes
3769 * second value on the variable stack, and pushes the result
3770 * Returns non-zero on error.
3772 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3773 WCHAR thisop;
3774 int var1,var2;
3775 int rc = 0;
3777 if (!*opstack || !*varstack) {
3778 WINE_TRACE("No operators for the reduce\n");
3779 return WCMD_NOOPERATOR;
3782 /* Remove the top operator */
3783 thisop = WCMD_popoperator(opstack);
3784 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
3786 /* One variable operators */
3787 var1 = WCMD_popnumber(varstack);
3788 switch (thisop) {
3789 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3790 break;
3791 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3792 break;
3793 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3794 break;
3795 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3796 break;
3799 /* Two variable operators */
3800 if (!*varstack) {
3801 WINE_TRACE("No operands left for the reduce?\n");
3802 return WCMD_NOOPERAND;
3804 switch (thisop) {
3805 case '!':
3806 case '~':
3807 case OP_POSITIVE:
3808 case OP_NEGATIVE:
3809 break; /* Handled above */
3810 case '*': var2 = WCMD_popnumber(varstack);
3811 WCMD_pushnumber(NULL, var2*var1, varstack);
3812 break;
3813 case '/': var2 = WCMD_popnumber(varstack);
3814 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3815 WCMD_pushnumber(NULL, var2/var1, varstack);
3816 break;
3817 case '+': var2 = WCMD_popnumber(varstack);
3818 WCMD_pushnumber(NULL, var2+var1, varstack);
3819 break;
3820 case '-': var2 = WCMD_popnumber(varstack);
3821 WCMD_pushnumber(NULL, var2-var1, varstack);
3822 break;
3823 case '&': var2 = WCMD_popnumber(varstack);
3824 WCMD_pushnumber(NULL, var2&var1, varstack);
3825 break;
3826 case '%': var2 = WCMD_popnumber(varstack);
3827 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3828 WCMD_pushnumber(NULL, var2%var1, varstack);
3829 break;
3830 case '^': var2 = WCMD_popnumber(varstack);
3831 WCMD_pushnumber(NULL, var2^var1, varstack);
3832 break;
3833 case '<': var2 = WCMD_popnumber(varstack);
3834 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3835 which differs from the compiler (for example gcc) so being explicit. */
3836 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3837 WCMD_pushnumber(NULL, 0, varstack);
3838 } else {
3839 WCMD_pushnumber(NULL, var2<<var1, varstack);
3841 break;
3842 case '>': var2 = WCMD_popnumber(varstack);
3843 WCMD_pushnumber(NULL, var2>>var1, varstack);
3844 break;
3845 case '|': var2 = WCMD_popnumber(varstack);
3846 WCMD_pushnumber(NULL, var2|var1, varstack);
3847 break;
3849 case OP_ASSSIGNMUL:
3850 case OP_ASSSIGNDIV:
3851 case OP_ASSSIGNMOD:
3852 case OP_ASSSIGNADD:
3853 case OP_ASSSIGNSUB:
3854 case OP_ASSSIGNAND:
3855 case OP_ASSSIGNNOT:
3856 case OP_ASSSIGNOR:
3857 case OP_ASSSIGNSHL:
3858 case OP_ASSSIGNSHR:
3860 int i = 0;
3862 /* The left of an equals must be one variable */
3863 if (!(*varstack) || (*varstack)->isnum) {
3864 return WCMD_NOOPERAND;
3867 /* Make the number stack grow by inserting the value of the variable */
3868 var2 = WCMD_peeknumber(varstack);
3869 WCMD_pushnumber(NULL, var2, varstack);
3870 WCMD_pushnumber(NULL, var1, varstack);
3872 /* Make the operand stack grow by pushing the assign operator plus the
3873 operator to perform */
3874 while (calcassignments[i].op != ' ' &&
3875 calcassignments[i].calculatedop != thisop) {
3876 i++;
3878 if (calcassignments[i].calculatedop == ' ') {
3879 WINE_ERR("Unexpected operator %c\n", thisop);
3880 return WCMD_NOOPERATOR;
3882 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3883 WCMD_pushoperator(calcassignments[i].op,
3884 WCMD_getprecedence(calcassignments[i].op), opstack);
3885 break;
3888 case '=':
3890 WCHAR intFormat[] = {'%','d','\0'};
3891 WCHAR result[MAXSTRING];
3893 /* Build the result, then push it onto the stack */
3894 sprintfW(result, intFormat, var1);
3895 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3896 wine_dbgstr_w(result));
3897 SetEnvironmentVariableW((*varstack)->variable, result);
3898 var2 = WCMD_popnumber(varstack);
3899 WCMD_pushnumber(NULL, var1, varstack);
3900 break;
3903 default: WINE_ERR("Unrecognized operator %c\n", thisop);
3906 return rc;
3910 /****************************************************************************
3911 * WCMD_handleExpression
3912 * Handles an expression provided to set /a - If it finds brackets, it uses
3913 * recursion to process the parts in brackets.
3915 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3917 static const WCHAR mathDelims[] = {' ','\t','(',')','!','~','-','*','/','%',
3918 '+','<','>','&','^','|','=',',','\0' };
3919 int rc = 0;
3920 WCHAR *pos;
3921 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3922 OPSTACK *opstackhead = NULL;
3923 VARSTACK *varstackhead = NULL;
3924 WCHAR foundhalf = 0;
3926 /* Initialize */
3927 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3928 pos = *expr;
3930 /* Iterate through until whole expression is processed */
3931 while (pos && *pos) {
3932 BOOL treatasnumber;
3934 /* Skip whitespace to get to the next character to process*/
3935 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3936 if (!*pos) goto exprreturn;
3938 /* If we have found anything other than an operator then it's a number/variable */
3939 if (strchrW(mathDelims, *pos) == NULL) {
3940 WCHAR *parmstart, *parm, *dupparm;
3941 WCHAR *nextpos;
3943 /* Cannot have an expression with var/number twice, without an operator
3944 in-between, nor or number following a half constructed << or >> operator */
3945 if (lastwasnumber || foundhalf) {
3946 rc = WCMD_NOOPERATOR;
3947 goto exprerrorreturn;
3949 lastwasnumber = TRUE;
3951 if (isdigitW(*pos)) {
3952 /* For a number - just push it onto the stack */
3953 int num = strtoulW(pos, &nextpos, 0);
3954 WCMD_pushnumber(NULL, num, &varstackhead);
3955 pos = nextpos;
3957 /* Verify the number was validly formed */
3958 if (*nextpos && (strchrW(mathDelims, *nextpos) == NULL)) {
3959 rc = WCMD_BADHEXOCT;
3960 goto exprerrorreturn;
3962 } else {
3964 /* For a variable - just push it onto the stack */
3965 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3966 dupparm = heap_strdupW(parm);
3967 WCMD_pushnumber(dupparm, 0, &varstackhead);
3968 pos = parmstart + strlenW(dupparm);
3970 continue;
3973 /* We have found an operator. Some operators are one character, some two, and the minus
3974 and plus signs need special processing as they can be either operators or just influence
3975 the parameter which follows them */
3976 if (foundhalf && (*pos != foundhalf)) {
3977 /* Badly constructed operator pair */
3978 rc = WCMD_NOOPERATOR;
3979 goto exprerrorreturn;
3982 treatasnumber = FALSE; /* We are processing an operand */
3983 switch (*pos) {
3985 /* > and < are special as they are double character operators (and spaces can be between them!)
3986 If we see these for the first time, set a flag, and second time around we continue.
3987 Note these double character operators are stored as just one of the characters on the stack */
3988 case '>':
3989 case '<': if (!foundhalf) {
3990 foundhalf = *pos;
3991 pos++;
3992 break;
3994 /* We have found the rest, so clear up the knowledge of the half completed part and
3995 drop through to normal operator processing */
3996 foundhalf = 0;
3997 /* drop through */
3999 case '=': if (*pos=='=') {
4000 /* = is special cased as if the last was an operator then we may have e.g. += or
4001 *= etc which we need to handle by replacing the operator that is on the stack
4002 with a calculated assignment equivalent */
4003 if (!lastwasnumber && opstackhead) {
4004 int i = 0;
4005 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
4006 i++;
4008 if (calcassignments[i].op == ' ') {
4009 rc = WCMD_NOOPERAND;
4010 goto exprerrorreturn;
4011 } else {
4012 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
4013 when the general operator handling happens further down. */
4014 *pos = calcassignments[i].calculatedop;
4015 WCMD_popoperator(&opstackhead);
4019 /* Drop though */
4021 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
4022 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
4023 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
4024 /* drop through */
4025 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
4026 /* drop through */
4028 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
4029 case '!': /* drop through */
4030 case '~': /* drop through */
4031 case '/': /* drop through */
4032 case '%': /* drop through */
4033 case '&': /* drop through */
4034 case '^': /* drop through */
4035 case '*': /* drop through */
4036 case '|':
4037 /* General code for handling most of the operators - look at the
4038 precedence of the top item on the stack, and see if we need to
4039 action the stack before we push something else onto it. */
4041 int precedence = WCMD_getprecedence(*pos);
4042 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
4043 precedence, !opstackhead?-1:opstackhead->precedence);
4045 /* In general, for things with the same precedence, reduce immediately
4046 except for assignments and unary operators which do not */
4047 while (!rc && opstackhead &&
4048 ((opstackhead->precedence > precedence) ||
4049 ((opstackhead->precedence == precedence) &&
4050 (precedence != 1) && (precedence != 8)))) {
4051 rc = WCMD_reduce(&opstackhead, &varstackhead);
4053 if (rc) goto exprerrorreturn;
4054 WCMD_pushoperator(*pos, precedence, &opstackhead);
4055 pos++;
4056 break;
4059 /* comma means start a new expression, ie calculate what we have */
4060 case ',':
4062 int prevresult = -1;
4063 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
4064 while (!rc && opstackhead) {
4065 rc = WCMD_reduce(&opstackhead, &varstackhead);
4067 if (rc) goto exprerrorreturn;
4068 /* If we have anything other than one number left, error
4069 otherwise throw the number away */
4070 if (!varstackhead || varstackhead->next) {
4071 rc = WCMD_NOOPERATOR;
4072 goto exprerrorreturn;
4074 prevresult = WCMD_popnumber(&varstackhead);
4075 WINE_TRACE("Expression resolved to %d\n", prevresult);
4076 heap_free(varstackhead);
4077 varstackhead = NULL;
4078 pos++;
4079 break;
4082 /* Open bracket - use iteration to parse the inner expression, then continue */
4083 case '(' : {
4084 int exprresult = 0;
4085 pos++;
4086 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
4087 if (rc) goto exprerrorreturn;
4088 WCMD_pushnumber(NULL, exprresult, &varstackhead);
4089 break;
4092 /* Close bracket - we have finished this depth, calculate and return */
4093 case ')' : {
4094 pos++;
4095 treatasnumber = TRUE; /* Things in brackets result in a number */
4096 if (depth == 0) {
4097 rc = WCMD_BADPAREN;
4098 goto exprerrorreturn;
4100 goto exprreturn;
4103 default:
4104 WINE_ERR("Unrecognized operator %c\n", *pos);
4105 pos++;
4107 lastwasnumber = treatasnumber;
4110 exprreturn:
4111 *expr = pos;
4113 /* We need to reduce until we have a single number (or variable) on the
4114 stack and set the return value to that */
4115 while (!rc && opstackhead) {
4116 rc = WCMD_reduce(&opstackhead, &varstackhead);
4118 if (rc) goto exprerrorreturn;
4120 /* If we have anything other than one number left, error
4121 otherwise throw the number away */
4122 if (!varstackhead || varstackhead->next) {
4123 rc = WCMD_NOOPERATOR;
4124 goto exprerrorreturn;
4127 /* Now get the number (and convert if it's just a variable name) */
4128 *ret = WCMD_popnumber(&varstackhead);
4130 exprerrorreturn:
4131 /* Free all remaining memory */
4132 while (opstackhead) WCMD_popoperator(&opstackhead);
4133 while (varstackhead) WCMD_popnumber(&varstackhead);
4135 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
4136 return rc;
4139 /****************************************************************************
4140 * WCMD_setshow_env
4142 * Set/Show the environment variables
4145 void WCMD_setshow_env (WCHAR *s) {
4147 LPVOID env;
4148 WCHAR *p;
4149 BOOL status;
4150 static const WCHAR parmP[] = {'/','P','\0'};
4151 static const WCHAR parmA[] = {'/','A','\0'};
4152 WCHAR string[MAXSTRING];
4154 if (param1[0] == 0x00 && quals[0] == 0x00) {
4155 env = GetEnvironmentStringsW();
4156 WCMD_setshow_sortenv( env, NULL );
4157 return;
4160 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4161 if (CompareStringW(LOCALE_USER_DEFAULT,
4162 NORM_IGNORECASE | SORT_STRINGSORT,
4163 s, 2, parmP, -1) == CSTR_EQUAL) {
4164 DWORD count;
4166 s += 2;
4167 while (*s && (*s==' ' || *s=='\t')) s++;
4168 /* set /P "var=value"jim ignores anything after the last quote */
4169 if (*s=='\"') {
4170 WCHAR *lastquote;
4171 lastquote = WCMD_strip_quotes(s);
4172 if (lastquote) *lastquote = 0x00;
4173 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4176 /* If no parameter, or no '=' sign, return an error */
4177 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
4178 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4179 return;
4182 /* Output the prompt */
4183 *p++ = '\0';
4184 if (strlenW(p) != 0) WCMD_output_asis(p);
4186 /* Read the reply */
4187 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
4188 if (count > 1) {
4189 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4190 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4191 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4192 wine_dbgstr_w(string));
4193 SetEnvironmentVariableW(s, string);
4196 /* See if /A supplied, and if so calculate the results of all the expressions */
4197 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4198 NORM_IGNORECASE | SORT_STRINGSORT,
4199 s, 2, parmA, -1) == CSTR_EQUAL) {
4200 /* /A supplied, so evaluate expressions and set variables appropriately */
4201 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4202 /* of the final computation */
4203 int result = 0;
4204 int rc = 0;
4205 WCHAR *thisexpr;
4206 WCHAR *src,*dst;
4208 /* Remove all quotes before doing any calculations */
4209 thisexpr = heap_alloc((strlenW(s+2)+1) * sizeof(WCHAR));
4210 src = s+2;
4211 dst = thisexpr;
4212 while (*src) {
4213 if (*src != '"') *dst++ = *src;
4214 src++;
4216 *dst = 0;
4218 /* Now calculate the results of the expression */
4219 src = thisexpr;
4220 rc = WCMD_handleExpression(&src, &result, 0);
4221 heap_free(thisexpr);
4223 /* If parsing failed, issue the error message */
4224 if (rc > 0) {
4225 WCMD_output_stderr(WCMD_LoadMessage(rc));
4226 return;
4229 /* If we have no context (interactive or cmd.exe /c) print the final result */
4230 if (!context) {
4231 static const WCHAR fmt[] = {'%','d','\0'};
4232 sprintfW(string, fmt, result);
4233 WCMD_output_asis(string);
4236 } else {
4237 DWORD gle;
4239 /* set "var=value"jim ignores anything after the last quote */
4240 if (*s=='\"') {
4241 WCHAR *lastquote;
4242 lastquote = WCMD_strip_quotes(s);
4243 if (lastquote) *lastquote = 0x00;
4244 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4247 p = strchrW (s, '=');
4248 if (p == NULL) {
4249 env = GetEnvironmentStringsW();
4250 if (WCMD_setshow_sortenv( env, s ) == 0) {
4251 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4252 errorlevel = 1;
4254 return;
4256 *p++ = '\0';
4258 if (strlenW(p) == 0) p = NULL;
4259 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4260 wine_dbgstr_w(p));
4261 status = SetEnvironmentVariableW(s, p);
4262 gle = GetLastError();
4263 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4264 errorlevel = 1;
4265 } else if (!status) WCMD_print_error();
4266 else errorlevel = 0;
4270 /****************************************************************************
4271 * WCMD_setshow_path
4273 * Set/Show the path environment variable
4276 void WCMD_setshow_path (const WCHAR *args) {
4278 WCHAR string[1024];
4279 DWORD status;
4280 static const WCHAR pathW[] = {'P','A','T','H','\0'};
4281 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
4283 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
4284 status = GetEnvironmentVariableW(pathW, string, ARRAY_SIZE(string));
4285 if (status != 0) {
4286 WCMD_output_asis ( pathEqW);
4287 WCMD_output_asis ( string);
4288 WCMD_output_asis ( newlineW);
4290 else {
4291 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4294 else {
4295 if (*args == '=') args++; /* Skip leading '=' */
4296 status = SetEnvironmentVariableW(pathW, args);
4297 if (!status) WCMD_print_error();
4301 /****************************************************************************
4302 * WCMD_setshow_prompt
4304 * Set or show the command prompt.
4307 void WCMD_setshow_prompt (void) {
4309 WCHAR *s;
4310 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
4312 if (strlenW(param1) == 0) {
4313 SetEnvironmentVariableW(promptW, NULL);
4315 else {
4316 s = param1;
4317 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4318 if (strlenW(s) == 0) {
4319 SetEnvironmentVariableW(promptW, NULL);
4321 else SetEnvironmentVariableW(promptW, s);
4325 /****************************************************************************
4326 * WCMD_setshow_time
4328 * Set/Show the system time
4329 * FIXME: Can't change time yet
4332 void WCMD_setshow_time (void) {
4334 WCHAR curtime[64], buffer[64];
4335 DWORD count;
4336 SYSTEMTIME st;
4337 static const WCHAR parmT[] = {'/','T','\0'};
4339 if (strlenW(param1) == 0) {
4340 GetLocalTime(&st);
4341 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, curtime, ARRAY_SIZE(curtime))) {
4342 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4343 if (strstrW (quals, parmT) == NULL) {
4344 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4345 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
4346 if (count > 2) {
4347 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4351 else WCMD_print_error ();
4353 else {
4354 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4358 /****************************************************************************
4359 * WCMD_shift
4361 * Shift batch parameters.
4362 * Optional /n says where to start shifting (n=0-8)
4365 void WCMD_shift (const WCHAR *args) {
4366 int start;
4368 if (context != NULL) {
4369 WCHAR *pos = strchrW(args, '/');
4370 int i;
4372 if (pos == NULL) {
4373 start = 0;
4374 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4375 start = (*(pos+1) - '0');
4376 } else {
4377 SetLastError(ERROR_INVALID_PARAMETER);
4378 WCMD_print_error();
4379 return;
4382 WINE_TRACE("Shifting variables, starting at %d\n", start);
4383 for (i=start;i<=8;i++) {
4384 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4386 context -> shift_count[9] = context -> shift_count[9] + 1;
4391 /****************************************************************************
4392 * WCMD_start
4394 void WCMD_start(WCHAR *args)
4396 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
4397 '\\','s','t','a','r','t','.','e','x','e',0};
4398 static const WCHAR startDelims[] = { ' ', '\t', '/', '\0' };
4399 static const WCHAR prefixQuote[] = {'"','\\','"','\0'};
4400 static const WCHAR postfixQuote[] = {'\\','"','"','\0'};
4401 int argno;
4402 int have_title;
4403 WCHAR file[MAX_PATH];
4404 WCHAR *cmdline, *cmdline_params;
4405 STARTUPINFOW st;
4406 PROCESS_INFORMATION pi;
4408 GetWindowsDirectoryW( file, MAX_PATH );
4409 strcatW( file, exeW );
4410 cmdline = heap_alloc( (strlenW(file) + strlenW(args) + 8) * sizeof(WCHAR) );
4411 strcpyW( cmdline, file );
4412 strcatW( cmdline, spaceW );
4413 cmdline_params = cmdline + strlenW(cmdline);
4415 /* The start built-in has some special command-line parsing properties
4416 * which will be outlined here.
4418 * both '\t' and ' ' are argument separators
4419 * '/' has a special double role as both separator and switch prefix, e.g.
4421 * > start /low/i
4422 * or
4423 * > start "title"/i
4425 * are valid ways to pass multiple options to start. In the latter case
4426 * '/i' is not a part of the title but parsed as a switch.
4428 * However, '=', ';' and ',' are not separators:
4429 * > start "deus"=ex,machina
4431 * will in fact open a console titled 'deus=ex,machina'
4433 * The title argument parsing code is only interested in quotes themselves,
4434 * it does not respect escaping of any kind and all quotes are dropped
4435 * from the resulting title, therefore:
4437 * > start "\"" hello"/low
4439 * actually opens a console titled '\ hello' with low priorities.
4441 * To not break compatibility with wine programs relying on
4442 * wine's separate 'start.exe', this program's peculiar console
4443 * title parsing is actually implemented in 'cmd.exe' which is the
4444 * application native Windows programs will use to invoke 'start'.
4446 * WCMD_parameter_with_delims will take care of everything for us.
4448 have_title = FALSE;
4449 for (argno=0; ; argno++) {
4450 WCHAR *thisArg, *argN;
4452 argN = NULL;
4453 thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, startDelims);
4455 /* No more parameters */
4456 if (!argN)
4457 break;
4459 /* Found the title */
4460 if (argN[0] == '"') {
4461 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
4462 have_title = TRUE;
4464 /* Copy all of the cmdline processed */
4465 memcpy(cmdline_params, args, sizeof(WCHAR) * (argN - args));
4466 cmdline_params[argN - args] = '\0';
4468 /* Add quoted title */
4469 strcatW(cmdline_params, prefixQuote);
4470 strcatW(cmdline_params, thisArg);
4471 strcatW(cmdline_params, postfixQuote);
4473 /* Concatenate remaining command-line */
4474 thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, startDelims);
4475 strcatW(cmdline_params, argN + strlenW(thisArg));
4477 break;
4480 /* Skipping a regular argument? */
4481 else if (argN != args && argN[-1] == '/') {
4482 continue;
4484 /* Not an argument nor the title, start of program arguments,
4485 * stop looking for title.
4487 } else
4488 break;
4491 /* build command-line if not built yet */
4492 if (!have_title) {
4493 strcatW( cmdline, args );
4496 memset( &st, 0, sizeof(STARTUPINFOW) );
4497 st.cb = sizeof(STARTUPINFOW);
4499 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4501 WaitForSingleObject( pi.hProcess, INFINITE );
4502 GetExitCodeProcess( pi.hProcess, &errorlevel );
4503 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4504 CloseHandle(pi.hProcess);
4505 CloseHandle(pi.hThread);
4507 else
4509 SetLastError(ERROR_FILE_NOT_FOUND);
4510 WCMD_print_error ();
4511 errorlevel = 9009;
4513 heap_free(cmdline);
4516 /****************************************************************************
4517 * WCMD_title
4519 * Set the console title
4521 void WCMD_title (const WCHAR *args) {
4522 SetConsoleTitleW(args);
4525 /****************************************************************************
4526 * WCMD_type
4528 * Copy a file to standard output.
4531 void WCMD_type (WCHAR *args) {
4533 int argno = 0;
4534 WCHAR *argN = args;
4535 BOOL writeHeaders = FALSE;
4537 if (param1[0] == 0x00) {
4538 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4539 return;
4542 if (param2[0] != 0x00) writeHeaders = TRUE;
4544 /* Loop through all args */
4545 errorlevel = 0;
4546 while (argN) {
4547 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4549 HANDLE h;
4550 WCHAR buffer[512];
4551 DWORD count;
4553 if (!argN) break;
4555 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4556 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4557 FILE_ATTRIBUTE_NORMAL, NULL);
4558 if (h == INVALID_HANDLE_VALUE) {
4559 WCMD_print_error ();
4560 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4561 errorlevel = 1;
4562 } else {
4563 if (writeHeaders) {
4564 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
4565 WCMD_output(fmt, thisArg);
4567 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer) - 1, &count)) {
4568 if (count == 0) break; /* ReadFile reports success on EOF! */
4569 buffer[count] = 0;
4570 WCMD_output_asis (buffer);
4572 CloseHandle (h);
4577 /****************************************************************************
4578 * WCMD_more
4580 * Output either a file or stdin to screen in pages
4583 void WCMD_more (WCHAR *args) {
4585 int argno = 0;
4586 WCHAR *argN = args;
4587 WCHAR moreStr[100];
4588 WCHAR moreStrPage[100];
4589 WCHAR buffer[512];
4590 DWORD count;
4591 static const WCHAR moreStart[] = {'-','-',' ','\0'};
4592 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
4593 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
4594 ')',' ','-','-','\n','\0'};
4595 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
4597 /* Prefix the NLS more with '-- ', then load the text */
4598 errorlevel = 0;
4599 strcpyW(moreStr, moreStart);
4600 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3], ARRAY_SIZE(moreStr)-3);
4602 if (param1[0] == 0x00) {
4604 /* Wine implements pipes via temporary files, and hence stdin is
4605 effectively reading from the file. This means the prompts for
4606 more are satisfied by the next line from the input (file). To
4607 avoid this, ensure stdin is to the console */
4608 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4609 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
4610 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4611 FILE_ATTRIBUTE_NORMAL, 0);
4612 WINE_TRACE("No parms - working probably in pipe mode\n");
4613 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4615 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4616 once you get in this bit unless due to a pipe, it's going to end badly... */
4617 wsprintfW(moreStrPage, moreFmt, moreStr);
4619 WCMD_enter_paged_mode(moreStrPage);
4620 while (WCMD_ReadFile(hstdin, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4621 if (count == 0) break; /* ReadFile reports success on EOF! */
4622 buffer[count] = 0;
4623 WCMD_output_asis (buffer);
4625 WCMD_leave_paged_mode();
4627 /* Restore stdin to what it was */
4628 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4629 CloseHandle(hConIn);
4631 return;
4632 } else {
4633 BOOL needsPause = FALSE;
4635 /* Loop through all args */
4636 WINE_TRACE("Parms supplied - working through each file\n");
4637 WCMD_enter_paged_mode(moreStrPage);
4639 while (argN) {
4640 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4641 HANDLE h;
4643 if (!argN) break;
4645 if (needsPause) {
4647 /* Wait */
4648 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
4649 WCMD_leave_paged_mode();
4650 WCMD_output_asis(moreStrPage);
4651 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
4652 WCMD_enter_paged_mode(moreStrPage);
4656 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4657 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4658 FILE_ATTRIBUTE_NORMAL, NULL);
4659 if (h == INVALID_HANDLE_VALUE) {
4660 WCMD_print_error ();
4661 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4662 errorlevel = 1;
4663 } else {
4664 ULONG64 curPos = 0;
4665 ULONG64 fileLen = 0;
4666 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4668 /* Get the file size */
4669 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4670 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4672 needsPause = TRUE;
4673 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4674 if (count == 0) break; /* ReadFile reports success on EOF! */
4675 buffer[count] = 0;
4676 curPos += count;
4678 /* Update % count (would be used in WCMD_output_asis as prompt) */
4679 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
4681 WCMD_output_asis (buffer);
4683 CloseHandle (h);
4687 WCMD_leave_paged_mode();
4691 /****************************************************************************
4692 * WCMD_verify
4694 * Display verify flag.
4695 * FIXME: We don't actually do anything with the verify flag other than toggle
4696 * it...
4699 void WCMD_verify (const WCHAR *args) {
4701 int count;
4703 count = strlenW(args);
4704 if (count == 0) {
4705 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
4706 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
4707 return;
4709 if (lstrcmpiW(args, onW) == 0) {
4710 verify_mode = TRUE;
4711 return;
4713 else if (lstrcmpiW(args, offW) == 0) {
4714 verify_mode = FALSE;
4715 return;
4717 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4720 /****************************************************************************
4721 * WCMD_version
4723 * Display version info.
4726 void WCMD_version (void) {
4728 WCMD_output_asis (version_string);
4732 /****************************************************************************
4733 * WCMD_volume
4735 * Display volume information (set_label = FALSE)
4736 * Additionally set volume label (set_label = TRUE)
4737 * Returns 1 on success, 0 otherwise
4740 int WCMD_volume(BOOL set_label, const WCHAR *path)
4742 DWORD count, serial;
4743 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4744 BOOL status;
4746 if (strlenW(path) == 0) {
4747 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
4748 if (!status) {
4749 WCMD_print_error ();
4750 return 0;
4752 status = GetVolumeInformationW(NULL, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4754 else {
4755 static const WCHAR fmt[] = {'%','s','\\','\0'};
4756 if ((path[1] != ':') || (strlenW(path) != 2)) {
4757 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4758 return 0;
4760 wsprintfW (curdir, fmt, path);
4761 status = GetVolumeInformationW(curdir, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4763 if (!status) {
4764 WCMD_print_error ();
4765 return 0;
4767 if (label[0] != '\0') {
4768 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4769 curdir[0], label);
4771 else {
4772 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4773 curdir[0]);
4775 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4776 HIWORD(serial), LOWORD(serial));
4777 if (set_label) {
4778 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4779 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
4780 if (count > 1) {
4781 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4782 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4784 if (strlenW(path) != 0) {
4785 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4787 else {
4788 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4791 return 1;
4794 /**************************************************************************
4795 * WCMD_exit
4797 * Exit either the process, or just this batch program
4801 void WCMD_exit (CMD_LIST **cmdList) {
4803 static const WCHAR parmB[] = {'/','B','\0'};
4804 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
4806 if (context && lstrcmpiW(quals, parmB) == 0) {
4807 errorlevel = rc;
4808 context -> skip_rest = TRUE;
4809 *cmdList = NULL;
4810 } else {
4811 ExitProcess(rc);
4816 /*****************************************************************************
4817 * WCMD_assoc
4819 * Lists or sets file associations (assoc = TRUE)
4820 * Lists or sets file types (assoc = FALSE)
4822 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4824 HKEY key;
4825 DWORD accessOptions = KEY_READ;
4826 WCHAR *newValue;
4827 LONG rc = ERROR_SUCCESS;
4828 WCHAR keyValue[MAXSTRING];
4829 DWORD valueLen = MAXSTRING;
4830 HKEY readKey;
4831 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
4832 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
4834 /* See if parameter includes '=' */
4835 errorlevel = 0;
4836 newValue = strchrW(args, '=');
4837 if (newValue) accessOptions |= KEY_WRITE;
4839 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4840 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
4841 accessOptions, &key) != ERROR_SUCCESS) {
4842 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4843 return;
4846 /* If no parameters then list all associations */
4847 if (*args == 0x00) {
4848 int index = 0;
4850 /* Enumerate all the keys */
4851 while (rc != ERROR_NO_MORE_ITEMS) {
4852 WCHAR keyName[MAXSTRING];
4853 DWORD nameLen;
4855 /* Find the next value */
4856 nameLen = MAXSTRING;
4857 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4859 if (rc == ERROR_SUCCESS) {
4861 /* Only interested in extension ones if assoc, or others
4862 if not assoc */
4863 if ((keyName[0] == '.' && assoc) ||
4864 (!(keyName[0] == '.') && (!assoc)))
4866 WCHAR subkey[MAXSTRING];
4867 strcpyW(subkey, keyName);
4868 if (!assoc) strcatW(subkey, shOpCmdW);
4870 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4872 valueLen = ARRAY_SIZE(keyValue);
4873 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4874 WCMD_output_asis(keyName);
4875 WCMD_output_asis(equalW);
4876 /* If no default value found, leave line empty after '=' */
4877 if (rc == ERROR_SUCCESS) {
4878 WCMD_output_asis(keyValue);
4880 WCMD_output_asis(newlineW);
4881 RegCloseKey(readKey);
4887 } else {
4889 /* Parameter supplied - if no '=' on command line, it's a query */
4890 if (newValue == NULL) {
4891 WCHAR *space;
4892 WCHAR subkey[MAXSTRING];
4894 /* Query terminates the parameter at the first space */
4895 strcpyW(keyValue, args);
4896 space = strchrW(keyValue, ' ');
4897 if (space) *space=0x00;
4899 /* Set up key name */
4900 strcpyW(subkey, keyValue);
4901 if (!assoc) strcatW(subkey, shOpCmdW);
4903 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4905 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4906 WCMD_output_asis(args);
4907 WCMD_output_asis(equalW);
4908 /* If no default value found, leave line empty after '=' */
4909 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4910 WCMD_output_asis(newlineW);
4911 RegCloseKey(readKey);
4913 } else {
4914 WCHAR msgbuffer[MAXSTRING];
4916 /* Load the translated 'File association not found' */
4917 if (assoc) {
4918 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4919 } else {
4920 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4922 WCMD_output_stderr(msgbuffer, keyValue);
4923 errorlevel = 2;
4926 /* Not a query - it's a set or clear of a value */
4927 } else {
4929 WCHAR subkey[MAXSTRING];
4931 /* Get pointer to new value */
4932 *newValue = 0x00;
4933 newValue++;
4935 /* Set up key name */
4936 strcpyW(subkey, args);
4937 if (!assoc) strcatW(subkey, shOpCmdW);
4939 /* If nothing after '=' then clear value - only valid for ASSOC */
4940 if (*newValue == 0x00) {
4942 if (assoc) rc = RegDeleteKeyW(key, args);
4943 if (assoc && rc == ERROR_SUCCESS) {
4944 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4946 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4947 WCMD_print_error();
4948 errorlevel = 2;
4950 } else {
4951 WCHAR msgbuffer[MAXSTRING];
4953 /* Load the translated 'File association not found' */
4954 if (assoc) {
4955 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4956 } else {
4957 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4959 WCMD_output_stderr(msgbuffer, args);
4960 errorlevel = 2;
4963 /* It really is a set value = contents */
4964 } else {
4965 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4966 accessOptions, NULL, &readKey, NULL);
4967 if (rc == ERROR_SUCCESS) {
4968 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4969 (LPBYTE)newValue,
4970 sizeof(WCHAR) * (strlenW(newValue) + 1));
4971 RegCloseKey(readKey);
4974 if (rc != ERROR_SUCCESS) {
4975 WCMD_print_error();
4976 errorlevel = 2;
4977 } else {
4978 WCMD_output_asis(args);
4979 WCMD_output_asis(equalW);
4980 WCMD_output_asis(newValue);
4981 WCMD_output_asis(newlineW);
4987 /* Clean up */
4988 RegCloseKey(key);
4991 /****************************************************************************
4992 * WCMD_color
4994 * Colors the terminal screen.
4997 void WCMD_color (void) {
4999 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
5000 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
5002 if (param1[0] != 0x00 && strlenW(param1) > 2) {
5003 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
5004 return;
5007 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
5009 COORD topLeft;
5010 DWORD screenSize;
5011 DWORD color = 0;
5013 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
5015 topLeft.X = 0;
5016 topLeft.Y = 0;
5018 /* Convert the color hex digits */
5019 if (param1[0] == 0x00) {
5020 color = defaultColor;
5021 } else {
5022 color = strtoulW(param1, NULL, 16);
5025 /* Fail if fg == bg color */
5026 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
5027 errorlevel = 1;
5028 return;
5031 /* Set the current screen contents and ensure all future writes
5032 remain this color */
5033 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
5034 SetConsoleTextAttribute(hStdOut, color);
5038 /****************************************************************************
5039 * WCMD_mklink
5042 void WCMD_mklink(WCHAR *args)
5044 int argno = 0;
5045 WCHAR *argN = args;
5046 BOOL isdir = FALSE;
5047 BOOL junction = FALSE;
5048 BOOL hard = FALSE;
5049 BOOL ret = FALSE;
5050 WCHAR file1[MAX_PATH];
5051 WCHAR file2[MAX_PATH];
5052 static const WCHAR optD[] = {'/', 'D', '\0'};
5053 static const WCHAR optH[] = {'/', 'H', '\0'};
5054 static const WCHAR optJ[] = {'/', 'J', '\0'};
5056 if (param1[0] == 0x00 || param2[0] == 0x00) {
5057 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
5058 return;
5061 file1[0] = 0;
5063 while (argN) {
5064 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
5066 if (!argN) break;
5068 WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
5070 if(lstrcmpiW(thisArg, optD) == 0)
5071 isdir = TRUE;
5072 else if(lstrcmpiW(thisArg, optH) == 0)
5073 hard = TRUE;
5074 else if(lstrcmpiW(thisArg, optJ) == 0)
5075 junction = TRUE;
5076 else {
5077 if(!file1[0])
5078 lstrcpyW(file1, thisArg);
5079 else
5080 lstrcpyW(file2, thisArg);
5084 if(hard)
5085 ret = CreateHardLinkW(file1, file2, NULL);
5086 else if(!junction)
5087 ret = CreateSymbolicLinkW(file1, file2, isdir);
5088 else
5089 WINE_TRACE("Juction links currently not supported.\n");
5091 if(!ret)
5092 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), file1);