cmd: Don't write outside of variable array.
[wine.git] / programs / cmd / builtins.c
blob4856b6198127651a284138533d12aea1dcdf6715
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 inbuilt[][10] = {
43 L"CALL",
44 L"CD",
45 L"CHDIR",
46 L"CLS",
47 L"COPY",
48 L"CTTY",
49 L"DATE",
50 L"DEL",
51 L"DIR",
52 L"ECHO",
53 L"ERASE",
54 L"FOR",
55 L"GOTO",
56 L"HELP",
57 L"IF",
58 L"LABEL",
59 L"MD",
60 L"MKDIR",
61 L"MOVE",
62 L"PATH",
63 L"PAUSE",
64 L"PROMPT",
65 L"REM",
66 L"REN",
67 L"RENAME",
68 L"RD",
69 L"RMDIR",
70 L"SET",
71 L"SHIFT",
72 L"START",
73 L"TIME",
74 L"TITLE",
75 L"TYPE",
76 L"VERIFY",
77 L"VER",
78 L"VOL",
79 L"ENDLOCAL",
80 L"SETLOCAL",
81 L"PUSHD",
82 L"POPD",
83 L"ASSOC",
84 L"COLOR",
85 L"FTYPE",
86 L"MORE",
87 L"CHOICE",
88 L"MKLINK",
89 L"EXIT"
91 static const WCHAR externals[][10] = {
92 L"ATTRIB",
93 L"XCOPY"
96 static HINSTANCE hinst;
97 struct env_stack *saved_environment;
98 static BOOL verify_mode = FALSE;
100 /* set /a routines work from single character operators, but some of the
101 operators are multiple character ones, especially the assignment ones.
102 Temporarily represent these using the values below on the operator stack */
103 #define OP_POSITIVE 'P'
104 #define OP_NEGATIVE 'N'
105 #define OP_ASSSIGNMUL 'a'
106 #define OP_ASSSIGNDIV 'b'
107 #define OP_ASSSIGNMOD 'c'
108 #define OP_ASSSIGNADD 'd'
109 #define OP_ASSSIGNSUB 'e'
110 #define OP_ASSSIGNAND 'f'
111 #define OP_ASSSIGNNOT 'g'
112 #define OP_ASSSIGNOR 'h'
113 #define OP_ASSSIGNSHL 'i'
114 #define OP_ASSSIGNSHR 'j'
116 /* This maintains a stack of operators, holding both the operator precedence
117 and the single character representation of the operator in question */
118 typedef struct _OPSTACK
120 int precedence;
121 WCHAR op;
122 struct _OPSTACK *next;
123 } OPSTACK;
125 /* This maintains a stack of values, where each value can either be a
126 numeric value, or a string representing an environment variable */
127 typedef struct _VARSTACK
129 BOOL isnum;
130 WCHAR *variable;
131 int value;
132 struct _VARSTACK *next;
133 } VARSTACK;
135 /* This maintains a mapping between the calculated operator and the
136 single character representation for the assignment operators. */
137 static struct
139 WCHAR op;
140 WCHAR calculatedop;
141 } calcassignments[] =
143 {'*', OP_ASSSIGNMUL},
144 {'/', OP_ASSSIGNDIV},
145 {'%', OP_ASSSIGNMOD},
146 {'+', OP_ASSSIGNADD},
147 {'-', OP_ASSSIGNSUB},
148 {'&', OP_ASSSIGNAND},
149 {'^', OP_ASSSIGNNOT},
150 {'|', OP_ASSSIGNOR},
151 {'<', OP_ASSSIGNSHL},
152 {'>', OP_ASSSIGNSHR},
153 {' ',' '}
156 /**************************************************************************
157 * WCMD_ask_confirm
159 * Issue a message and ask for confirmation, waiting on a valid answer.
161 * Returns True if Y (or A) answer is selected
162 * If optionAll contains a pointer, ALL is allowed, and if answered
163 * set to TRUE
166 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
167 BOOL *optionAll) {
169 UINT msgid;
170 WCHAR confirm[MAXSTRING];
171 WCHAR options[MAXSTRING];
172 WCHAR Ybuffer[MAXSTRING];
173 WCHAR Nbuffer[MAXSTRING];
174 WCHAR Abuffer[MAXSTRING];
175 WCHAR answer[MAX_PATH] = {'\0'};
176 DWORD count = 0;
178 /* Load the translated valid answers */
179 if (showSureText)
180 LoadStringW(hinst, WCMD_CONFIRM, confirm, ARRAY_SIZE(confirm));
181 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
182 LoadStringW(hinst, msgid, options, ARRAY_SIZE(options));
183 LoadStringW(hinst, WCMD_YES, Ybuffer, ARRAY_SIZE(Ybuffer));
184 LoadStringW(hinst, WCMD_NO, Nbuffer, ARRAY_SIZE(Nbuffer));
185 LoadStringW(hinst, WCMD_ALL, Abuffer, ARRAY_SIZE(Abuffer));
187 /* Loop waiting on a valid answer */
188 if (optionAll)
189 *optionAll = FALSE;
190 while (1)
192 WCMD_output_asis (message);
193 if (showSureText)
194 WCMD_output_asis (confirm);
195 WCMD_output_asis (options);
196 if (!WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, ARRAY_SIZE(answer), &count))
197 return FALSE;
198 answer[0] = towupper(answer[0]);
199 if (answer[0] == Ybuffer[0])
200 return TRUE;
201 if (answer[0] == Nbuffer[0])
202 return FALSE;
203 if (optionAll && answer[0] == Abuffer[0])
205 *optionAll = TRUE;
206 return TRUE;
211 /****************************************************************************
212 * WCMD_clear_screen
214 * Clear the terminal screen.
217 void WCMD_clear_screen (void) {
219 /* Emulate by filling the screen from the top left to bottom right with
220 spaces, then moving the cursor to the top left afterwards */
221 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
222 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
224 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
226 COORD topLeft;
227 DWORD screenSize, written;
229 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
231 topLeft.X = 0;
232 topLeft.Y = 0;
233 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
234 FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
235 SetConsoleCursorPosition(hStdOut, topLeft);
239 /****************************************************************************
240 * WCMD_change_tty
242 * Change the default i/o device (ie redirect STDin/STDout).
245 void WCMD_change_tty (void) {
247 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
251 /****************************************************************************
252 * WCMD_choice
256 void WCMD_choice (const WCHAR * args) {
257 WCHAR answer[16];
258 WCHAR buffer[16];
259 WCHAR *ptr = NULL;
260 WCHAR *opt_c = NULL;
261 WCHAR *my_command = NULL;
262 WCHAR opt_default = 0;
263 DWORD opt_timeout = 0;
264 DWORD count;
265 DWORD oldmode;
266 BOOL have_console;
267 BOOL opt_n = FALSE;
268 BOOL opt_s = FALSE;
270 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
271 errorlevel = 0;
273 my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
275 ptr = WCMD_skip_leading_spaces(my_command);
276 while (*ptr == '/') {
277 switch (towupper(ptr[1])) {
278 case 'C':
279 ptr += 2;
280 /* the colon is optional */
281 if (*ptr == ':')
282 ptr++;
284 if (!*ptr || iswspace(*ptr)) {
285 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
286 heap_free(my_command);
287 return;
290 /* remember the allowed keys (overwrite previous /C option) */
291 opt_c = ptr;
292 while (*ptr && (!iswspace(*ptr)))
293 ptr++;
295 if (*ptr) {
296 /* terminate allowed chars */
297 *ptr = 0;
298 ptr = WCMD_skip_leading_spaces(&ptr[1]);
300 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
301 break;
303 case 'N':
304 opt_n = TRUE;
305 ptr = WCMD_skip_leading_spaces(&ptr[2]);
306 break;
308 case 'S':
309 opt_s = TRUE;
310 ptr = WCMD_skip_leading_spaces(&ptr[2]);
311 break;
313 case 'T':
314 ptr = &ptr[2];
315 /* the colon is optional */
316 if (*ptr == ':')
317 ptr++;
319 opt_default = *ptr++;
321 if (!opt_default || (*ptr != ',')) {
322 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
323 heap_free(my_command);
324 return;
326 ptr++;
328 count = 0;
329 while (((answer[count] = *ptr)) && iswdigit(*ptr) && (count < 15)) {
330 count++;
331 ptr++;
334 answer[count] = 0;
335 opt_timeout = wcstol(answer, NULL, 10);
337 ptr = WCMD_skip_leading_spaces(ptr);
338 break;
340 default:
341 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
342 heap_free(my_command);
343 return;
347 if (opt_timeout)
348 WINE_FIXME("timeout not supported: %c,%ld\n", opt_default, opt_timeout);
350 if (have_console)
351 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
353 /* use default keys, when needed: localized versions of "Y"es and "No" */
354 if (!opt_c) {
355 LoadStringW(hinst, WCMD_YES, buffer, ARRAY_SIZE(buffer));
356 LoadStringW(hinst, WCMD_NO, buffer + 1, ARRAY_SIZE(buffer) - 1);
357 opt_c = buffer;
358 buffer[2] = 0;
361 /* print the question, when needed */
362 if (*ptr)
363 WCMD_output_asis(ptr);
365 if (!opt_s) {
366 wcsupr(opt_c);
367 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
370 if (!opt_n) {
371 /* print a list of all allowed answers inside brackets */
372 WCMD_output_asis(L"[");
373 ptr = opt_c;
374 answer[1] = 0;
375 while ((answer[0] = *ptr++)) {
376 WCMD_output_asis(answer);
377 if (*ptr)
378 WCMD_output_asis(L",");
380 WCMD_output_asis(L"]?");
383 while (TRUE) {
385 /* FIXME: Add support for option /T */
386 answer[1] = 0; /* terminate single character string */
387 if (!WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count))
389 heap_free(my_command);
390 errorlevel = 0;
391 return;
394 if (!opt_s)
395 answer[0] = towupper(answer[0]);
397 ptr = wcschr(opt_c, answer[0]);
398 if (ptr) {
399 WCMD_output_asis(answer);
400 WCMD_output_asis(L"\r\n");
401 if (have_console)
402 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
404 errorlevel = (ptr - opt_c) + 1;
405 WINE_TRACE("answer: %ld\n", errorlevel);
406 heap_free(my_command);
407 return;
409 else
411 /* key not allowed: play the bell */
412 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
413 WCMD_output_asis(L"\a");
418 /****************************************************************************
419 * WCMD_AppendEOF
421 * Adds an EOF onto the end of a file
422 * Returns TRUE on success
424 static BOOL WCMD_AppendEOF(WCHAR *filename)
426 HANDLE h;
427 DWORD bytes_written;
429 char eof = '\x1a';
431 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
432 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
433 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
435 if (h == INVALID_HANDLE_VALUE) {
436 WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(filename), GetLastError());
437 return FALSE;
438 } else {
439 SetFilePointer (h, 0, NULL, FILE_END);
440 if (!WriteFile(h, &eof, 1, &bytes_written, NULL)) {
441 WINE_ERR("Failed to append EOF to %s (%ld)\n", wine_dbgstr_w(filename), GetLastError());
442 CloseHandle(h);
443 return FALSE;
445 CloseHandle(h);
447 return TRUE;
450 /****************************************************************************
451 * WCMD_IsSameFile
453 * Checks if the two paths reference to the same file
455 static BOOL WCMD_IsSameFile(const WCHAR *name1, const WCHAR *name2)
457 BOOL ret = FALSE;
458 HANDLE file1 = INVALID_HANDLE_VALUE, file2 = INVALID_HANDLE_VALUE;
459 BY_HANDLE_FILE_INFORMATION info1, info2;
461 file1 = CreateFileW(name1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
462 if (file1 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file1, &info1))
463 goto end;
465 file2 = CreateFileW(name2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
466 if (file2 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file2, &info2))
467 goto end;
469 ret = info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
470 && info1.nFileIndexHigh == info2.nFileIndexHigh
471 && info1.nFileIndexLow == info2.nFileIndexLow;
472 end:
473 if (file1 != INVALID_HANDLE_VALUE)
474 CloseHandle(file1);
475 if (file2 != INVALID_HANDLE_VALUE)
476 CloseHandle(file2);
477 return ret;
480 /****************************************************************************
481 * WCMD_ManualCopy
483 * Copies from a file
484 * optionally reading only until EOF (ascii copy)
485 * optionally appending onto an existing file (append)
486 * Returns TRUE on success
488 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
490 HANDLE in,out;
491 BOOL ok;
492 DWORD bytesread, byteswritten;
494 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
495 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
497 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
498 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
499 if (in == INVALID_HANDLE_VALUE) {
500 WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(srcname), GetLastError());
501 return FALSE;
504 /* Open the output file, overwriting if not appending */
505 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
506 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
507 if (out == INVALID_HANDLE_VALUE) {
508 WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(dstname), GetLastError());
509 CloseHandle(in);
510 return FALSE;
513 /* Move to end of destination if we are going to append to it */
514 if (append) {
515 SetFilePointer(out, 0, NULL, FILE_END);
518 /* Loop copying data from source to destination until EOF read */
521 char buffer[MAXSTRING];
523 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
524 if (ok) {
526 /* Stop at first EOF */
527 if (ascii) {
528 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
529 if (ptr) bytesread = (ptr - buffer);
532 if (bytesread) {
533 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
534 if (!ok || byteswritten != bytesread) {
535 WINE_ERR("Unexpected failure writing to %s, rc=%ld\n",
536 wine_dbgstr_w(dstname), GetLastError());
539 } else {
540 WINE_ERR("Unexpected failure reading from %s, rc=%ld\n",
541 wine_dbgstr_w(srcname), GetLastError());
543 } while (ok && bytesread > 0);
545 CloseHandle(out);
546 CloseHandle(in);
547 return ok;
550 /****************************************************************************
551 * WCMD_copy
553 * Copy a file or wildcarded set.
554 * For ascii/binary type copies, it gets complex:
555 * Syntax on command line is
556 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
557 * Where first /a or /b sets 'mode in operation' until another is found
558 * once another is found, it applies to the file preceding the /a or /b
559 * In addition each filename can contain wildcards
560 * To make matters worse, the + may be in the same parameter (i.e. no
561 * whitespace) or with whitespace separating it
563 * ASCII mode on read == read and stop at first EOF
564 * ASCII mode on write == append EOF to destination
565 * Binary == copy as-is
567 * Design of this is to build up a list of files which will be copied into a
568 * list, then work through the list file by file.
569 * If no destination is specified, it defaults to the name of the first file in
570 * the list, but the current directory.
574 void WCMD_copy(WCHAR * args) {
576 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
577 WCHAR *thisparam;
578 int argno = 0;
579 WCHAR *rawarg;
580 WIN32_FIND_DATAW fd;
581 HANDLE hff = INVALID_HANDLE_VALUE;
582 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
583 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
584 BOOL anyconcats = FALSE; /* Have we found any + options */
585 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
586 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
587 BOOL prompt; /* Prompt before overwriting */
588 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
589 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
590 BOOL status;
591 WCHAR copycmd[4];
592 DWORD len;
593 BOOL dstisdevice = FALSE;
595 typedef struct _COPY_FILES
597 struct _COPY_FILES *next;
598 BOOL concatenate;
599 WCHAR *name;
600 int binarycopy;
601 } COPY_FILES;
602 COPY_FILES *sourcelist = NULL;
603 COPY_FILES *lastcopyentry = NULL;
604 COPY_FILES *destination = NULL;
605 COPY_FILES *thiscopy = NULL;
606 COPY_FILES *prevcopy = NULL;
608 /* Assume we were successful! */
609 errorlevel = 0;
611 /* If no args supplied at all, report an error */
612 if (param1[0] == 0x00) {
613 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
614 errorlevel = 1;
615 return;
618 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
620 /* Walk through all args, building up a list of files to process */
621 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
622 while (*(thisparam)) {
623 WCHAR *pos1, *pos2;
624 BOOL inquotes;
626 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
628 /* Handle switches */
629 if (*thisparam == '/') {
630 while (*thisparam == '/') {
631 thisparam++;
632 if (towupper(*thisparam) == 'D') {
633 opt_d = TRUE;
634 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
635 } else if (towupper(*thisparam) == 'Y') {
636 opt_y = TRUE;
637 } else if (towupper(*thisparam) == '-' && towupper(*(thisparam+1)) == 'Y') {
638 opt_noty = TRUE;
639 } else if (towupper(*thisparam) == 'V') {
640 opt_v = TRUE;
641 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
642 } else if (towupper(*thisparam) == 'N') {
643 opt_n = TRUE;
644 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
645 } else if (towupper(*thisparam) == 'Z') {
646 opt_z = TRUE;
647 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
648 } else if (towupper(*thisparam) == 'A') {
649 if (binarymode != 0) {
650 binarymode = 0;
651 WINE_TRACE("Subsequent files will be handled as ASCII\n");
652 if (destination != NULL) {
653 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
654 destination->binarycopy = binarymode;
655 } else if (lastcopyentry != NULL) {
656 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
657 lastcopyentry->binarycopy = binarymode;
660 } else if (towupper(*thisparam) == 'B') {
661 if (binarymode != 1) {
662 binarymode = 1;
663 WINE_TRACE("Subsequent files will be handled as binary\n");
664 if (destination != NULL) {
665 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
666 destination->binarycopy = binarymode;
667 } else if (lastcopyentry != NULL) {
668 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
669 lastcopyentry->binarycopy = binarymode;
672 } else {
673 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
675 thisparam++;
678 /* This parameter was purely switches, get the next one */
679 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
680 continue;
683 /* We have found something which is not a switch. If could be anything of the form
684 sourcefilename (which could be destination too)
685 + (when filename + filename syntex used)
686 sourcefilename+sourcefilename
687 +sourcefilename
688 +/b[tests show windows then ignores to end of parameter]
691 if (*thisparam=='+') {
692 if (lastcopyentry == NULL) {
693 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
694 errorlevel = 1;
695 goto exitreturn;
696 } else {
697 concatnextfilename = TRUE;
698 anyconcats = TRUE;
701 /* Move to next thing to process */
702 thisparam++;
703 if (*thisparam == 0x00)
704 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
705 continue;
708 /* We have found something to process - build a COPY_FILE block to store it */
709 thiscopy = heap_xalloc(sizeof(COPY_FILES));
711 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
712 thiscopy->concatenate = concatnextfilename;
713 thiscopy->binarycopy = binarymode;
714 thiscopy->next = NULL;
716 /* Time to work out the name. Allocate at least enough space (deliberately too much to
717 leave space to append \* to the end) , then copy in character by character. Strip off
718 quotes if we find them. */
719 len = lstrlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
720 thiscopy->name = heap_xalloc(len*sizeof(WCHAR));
721 memset(thiscopy->name, 0x00, len);
723 pos1 = thisparam;
724 pos2 = thiscopy->name;
725 inquotes = FALSE;
726 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
727 if (*pos1 == '"') {
728 inquotes = !inquotes;
729 pos1++;
730 } else *pos2++ = *pos1++;
732 *pos2 = 0;
733 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
735 /* This is either the first source, concatenated subsequent source or destination */
736 if (sourcelist == NULL) {
737 WINE_TRACE("Adding as first source part\n");
738 sourcelist = thiscopy;
739 lastcopyentry = thiscopy;
740 } else if (concatnextfilename) {
741 WINE_TRACE("Adding to source file list to be concatenated\n");
742 lastcopyentry->next = thiscopy;
743 lastcopyentry = thiscopy;
744 } else if (destination == NULL) {
745 destination = thiscopy;
746 } else {
747 /* We have processed sources and destinations and still found more to do - invalid */
748 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
749 errorlevel = 1;
750 goto exitreturn;
752 concatnextfilename = FALSE;
754 /* We either need to process the rest of the parameter or move to the next */
755 if (*pos1 == '/' || *pos1 == '+') {
756 thisparam = pos1;
757 continue;
758 } else {
759 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
763 /* Ensure we have at least one source file */
764 if (!sourcelist) {
765 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
766 errorlevel = 1;
767 goto exitreturn;
770 /* Default whether automatic overwriting is on. If we are interactive then
771 we prompt by default, otherwise we overwrite by default
772 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
773 if (opt_noty) prompt = TRUE;
774 else if (opt_y) prompt = FALSE;
775 else {
776 /* By default, we will force the overwrite in batch mode and ask for
777 * confirmation in interactive mode. */
778 prompt = interactive;
779 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
780 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
781 * default behavior. */
782 len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
783 if (len && len < ARRAY_SIZE(copycmd)) {
784 if (!lstrcmpiW(copycmd, L"/Y"))
785 prompt = FALSE;
786 else if (!lstrcmpiW(copycmd, L"/-Y"))
787 prompt = TRUE;
791 /* Calculate the destination now - if none supplied, it's current dir +
792 filename of first file in list*/
793 if (destination == NULL) {
795 WINE_TRACE("No destination supplied, so need to calculate it\n");
796 lstrcpyW(destname, L".");
797 lstrcatW(destname, L"\\");
799 destination = heap_xalloc(sizeof(COPY_FILES));
800 if (destination == NULL) goto exitreturn;
801 destination->concatenate = FALSE; /* Not used for destination */
802 destination->binarycopy = binarymode;
803 destination->next = NULL; /* Not used for destination */
804 destination->name = NULL; /* To be filled in */
805 destisdirectory = TRUE;
807 } else {
808 WCHAR *filenamepart;
809 DWORD attributes;
811 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
813 /* Convert to fully qualified path/filename */
814 if (!WCMD_get_fullpath(destination->name, ARRAY_SIZE(destname), destname, &filenamepart)) return;
815 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
817 /* If parameter is a directory, ensure it ends in \ */
818 attributes = GetFileAttributesW(destname);
819 if (ends_with_backslash( destname ) ||
820 ((attributes != INVALID_FILE_ATTRIBUTES) &&
821 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
823 destisdirectory = TRUE;
824 if (!ends_with_backslash(destname)) lstrcatW(destname, L"\\");
825 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
829 /* Normally, the destination is the current directory unless we are
830 concatenating, in which case it's current directory plus first filename.
831 Note that if the
832 In addition by default it is a binary copy unless concatenating, when
833 the copy defaults to an ascii copy (stop at EOF). We do not know the
834 first source part yet (until we search) so flag as needing filling in. */
836 if (anyconcats) {
837 /* We have found an a+b type syntax, so destination has to be a filename
838 and we need to default to ascii copying. If we have been supplied a
839 directory as the destination, we need to defer calculating the name */
840 if (destisdirectory) appendfirstsource = TRUE;
841 if (destination->binarycopy == -1) destination->binarycopy = 0;
843 } else if (!destisdirectory) {
844 /* We have been asked to copy to a filename. Default to ascii IF the
845 source contains wildcards (true even if only one match) */
846 if (wcspbrk(sourcelist->name, L"*?") != NULL) {
847 anyconcats = TRUE; /* We really are concatenating to a single file */
848 if (destination->binarycopy == -1) {
849 destination->binarycopy = 0;
851 } else {
852 if (destination->binarycopy == -1) {
853 destination->binarycopy = 1;
858 /* Save away the destination name*/
859 heap_free(destination->name);
860 destination->name = heap_strdupW(destname);
861 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
862 wine_dbgstr_w(destname), appendfirstsource);
864 /* Remember if the destination is a device */
865 if (wcsncmp(destination->name, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
866 WINE_TRACE("Destination is a device\n");
867 dstisdevice = TRUE;
870 /* Now we need to walk the set of sources, and process each name we come to.
871 If anyconcats is true, we are writing to one file, otherwise we are using
872 the source name each time.
873 If destination exists, prompt for overwrite the first time (if concatenating
874 we ask each time until yes is answered)
875 The first source file we come across must exist (when wildcards expanded)
876 and if concatenating with overwrite prompts, each source file must exist
877 until a yes is answered. */
879 thiscopy = sourcelist;
880 prevcopy = NULL;
882 while (thiscopy != NULL) {
884 WCHAR srcpath[MAX_PATH];
885 const WCHAR *srcname;
886 WCHAR *filenamepart;
887 DWORD attributes;
888 BOOL srcisdevice = FALSE;
890 /* If it was not explicit, we now know whether we are concatenating or not and
891 hence whether to copy as binary or ascii */
892 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
894 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
895 to where the filename portion begins (used for wildcard expansion). */
896 if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return;
897 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
899 /* If parameter is a directory, ensure it ends in \* */
900 attributes = GetFileAttributesW(srcpath);
901 if (ends_with_backslash( srcpath )) {
903 /* We need to know where the filename part starts, so append * and
904 recalculate the full resulting path */
905 lstrcatW(thiscopy->name, L"*");
906 if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return;
907 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
909 } else if ((wcspbrk(srcpath, L"*?") == NULL) &&
910 (attributes != INVALID_FILE_ATTRIBUTES) &&
911 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
913 /* We need to know where the filename part starts, so append \* and
914 recalculate the full resulting path */
915 lstrcatW(thiscopy->name, L"\\*");
916 if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return;
917 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
920 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
921 wine_dbgstr_w(srcpath), anyconcats);
923 /* If the source is a device, just use it, otherwise search */
924 if (wcsncmp(srcpath, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
925 WINE_TRACE("Source is a device\n");
926 srcisdevice = TRUE;
927 srcname = &srcpath[4]; /* After the \\.\ prefix */
928 } else {
930 /* Loop through all source files */
931 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
932 hff = FindFirstFileW(srcpath, &fd);
933 if (hff != INVALID_HANDLE_VALUE) {
934 srcname = fd.cFileName;
938 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
939 do {
940 WCHAR outname[MAX_PATH];
941 BOOL overwrite;
942 BOOL appendtofirstfile = FALSE;
944 /* Skip . and .., and directories */
945 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
946 WINE_TRACE("Skipping directories\n");
947 } else {
949 /* Build final destination name */
950 lstrcpyW(outname, destination->name);
951 if (destisdirectory || appendfirstsource) lstrcatW(outname, srcname);
953 /* Build source name */
954 if (!srcisdevice) lstrcpyW(filenamepart, srcname);
956 /* Do we just overwrite (we do if we are writing to a device) */
957 overwrite = !prompt;
958 if (dstisdevice || (anyconcats && writtenoneconcat)) {
959 overwrite = TRUE;
962 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
963 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
964 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
965 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
967 if (!writtenoneconcat) {
968 appendtofirstfile = anyconcats && WCMD_IsSameFile(srcpath, outname);
971 /* Prompt before overwriting */
972 if (appendtofirstfile) {
973 overwrite = TRUE;
974 } else if (!overwrite) {
975 DWORD attributes = GetFileAttributesW(outname);
976 if (attributes != INVALID_FILE_ATTRIBUTES) {
977 WCHAR* question;
978 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
979 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
980 LocalFree(question);
982 else overwrite = TRUE;
985 /* If we needed to save away the first filename, do it */
986 if (appendfirstsource && overwrite) {
987 heap_free(destination->name);
988 destination->name = heap_strdupW(outname);
989 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
990 appendfirstsource = FALSE;
991 destisdirectory = FALSE;
994 /* Do the copy as appropriate */
995 if (overwrite) {
996 if (anyconcats && WCMD_IsSameFile(srcpath, outname)) {
997 /* Silently skip if the destination file is also a source file */
998 status = TRUE;
999 } else if (anyconcats && writtenoneconcat) {
1000 if (thiscopy->binarycopy) {
1001 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
1002 } else {
1003 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
1005 } else if (!thiscopy->binarycopy) {
1006 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
1007 } else if (srcisdevice) {
1008 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
1009 } else {
1010 status = CopyFileW(srcpath, outname, FALSE);
1012 if (!status) {
1013 WCMD_print_error ();
1014 errorlevel = 1;
1015 } else {
1016 WINE_TRACE("Copied successfully\n");
1017 if (anyconcats) writtenoneconcat = TRUE;
1019 /* Append EOF if ascii destination and we are not going to add more onto the end
1020 Note: Testing shows windows has an optimization whereas if you have a binary
1021 copy of a file to a single destination (ie concatenation) then it does not add
1022 the EOF, hence the check on the source copy type below. */
1023 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
1024 if (!WCMD_AppendEOF(outname)) {
1025 WCMD_print_error ();
1026 errorlevel = 1;
1032 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1033 if (!srcisdevice) FindClose (hff);
1034 } else {
1035 /* Error if the first file was not found */
1036 if (!anyconcats || !writtenoneconcat) {
1037 WCMD_print_error ();
1038 errorlevel = 1;
1042 /* Step on to the next supplied source */
1043 thiscopy = thiscopy -> next;
1046 /* Append EOF if ascii destination and we were concatenating */
1047 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
1048 if (!WCMD_AppendEOF(destination->name)) {
1049 WCMD_print_error ();
1050 errorlevel = 1;
1054 /* Exit out of the routine, freeing any remaining allocated memory */
1055 exitreturn:
1057 thiscopy = sourcelist;
1058 while (thiscopy != NULL) {
1059 prevcopy = thiscopy;
1060 /* Free up this block*/
1061 thiscopy = thiscopy -> next;
1062 heap_free(prevcopy->name);
1063 heap_free(prevcopy);
1066 /* Free up the destination memory */
1067 if (destination) {
1068 heap_free(destination->name);
1069 heap_free(destination);
1072 return;
1075 /****************************************************************************
1076 * WCMD_create_dir
1078 * Create a directory (and, if needed, any intermediate directories).
1080 * Modifies its argument by replacing slashes temporarily with nulls.
1083 static BOOL create_full_path(WCHAR* path)
1085 WCHAR *p, *start;
1087 /* don't mess with drive letter portion of path, if any */
1088 start = path;
1089 if (path[1] == ':')
1090 start = path+2;
1092 /* Strip trailing slashes. */
1093 for (p = path + lstrlenW(path) - 1; p != start && *p == '\\'; p--)
1094 *p = 0;
1096 /* Step through path, creating intermediate directories as needed. */
1097 /* First component includes drive letter, if any. */
1098 p = start;
1099 for (;;) {
1100 DWORD rv;
1101 /* Skip to end of component */
1102 while (*p == '\\') p++;
1103 while (*p && *p != '\\') p++;
1104 if (!*p) {
1105 /* path is now the original full path */
1106 return CreateDirectoryW(path, NULL);
1108 /* Truncate path, create intermediate directory, and restore path */
1109 *p = 0;
1110 rv = CreateDirectoryW(path, NULL);
1111 *p = '\\';
1112 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1113 return FALSE;
1115 /* notreached */
1116 return FALSE;
1119 void WCMD_create_dir (WCHAR *args) {
1120 int argno = 0;
1121 WCHAR *argN = args;
1123 if (param1[0] == 0x00) {
1124 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1125 return;
1127 /* Loop through all args */
1128 while (TRUE) {
1129 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1130 if (!argN) break;
1131 if (!create_full_path(thisArg)) {
1132 WCMD_print_error ();
1133 errorlevel = 1;
1138 /* Parse the /A options given by the user on the commandline
1139 * into a bitmask of wanted attributes (*wantSet),
1140 * and a bitmask of unwanted attributes (*wantClear).
1142 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1143 WCHAR *p;
1145 /* both are strictly 'out' parameters */
1146 *wantSet=0;
1147 *wantClear=0;
1149 /* For each /A argument */
1150 for (p=wcsstr(quals, L"/A"); p != NULL; p=wcsstr(p, L"/A")) {
1151 /* Skip /A itself */
1152 p += 2;
1154 /* Skip optional : */
1155 if (*p == ':') p++;
1157 /* For each of the attribute specifier chars to this /A option */
1158 for (; *p != 0 && *p != '/'; p++) {
1159 BOOL negate = FALSE;
1160 DWORD mask = 0;
1162 if (*p == '-') {
1163 negate=TRUE;
1164 p++;
1167 /* Convert the attribute specifier to a bit in one of the masks */
1168 switch (*p) {
1169 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1170 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1171 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1172 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1173 default:
1174 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1176 if (negate)
1177 *wantClear |= mask;
1178 else
1179 *wantSet |= mask;
1184 /* If filename part of parameter is * or *.*,
1185 * and neither /Q nor /P options were given,
1186 * prompt the user whether to proceed.
1187 * Returns FALSE if user says no, TRUE otherwise.
1188 * *pPrompted is set to TRUE if the user is prompted.
1189 * (If /P supplied, del will prompt for individual files later.)
1191 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1192 if ((wcsstr(quals, L"/Q") == NULL) && (wcsstr(quals, L"/P") == NULL)) {
1193 WCHAR drive[10];
1194 WCHAR dir[MAX_PATH];
1195 WCHAR fname[MAX_PATH];
1196 WCHAR ext[MAX_PATH];
1197 WCHAR fpath[MAX_PATH];
1199 /* Convert path into actual directory spec */
1200 if (!WCMD_get_fullpath(filename, ARRAY_SIZE(fpath), fpath, NULL)) return FALSE;
1201 _wsplitpath(fpath, drive, dir, fname, ext);
1203 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1204 if ((lstrcmpW(fname, L"*") == 0) && (*ext == 0x00 || (lstrcmpW(ext, L".*") == 0))) {
1206 WCHAR question[MAXSTRING];
1208 /* Caller uses this to suppress "file not found" warning later */
1209 *pPrompted = TRUE;
1211 /* Ask for confirmation */
1212 wsprintfW(question, L"%s ", fpath);
1213 return WCMD_ask_confirm(question, TRUE, NULL);
1216 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1217 return TRUE;
1220 /* Helper function for WCMD_delete().
1221 * Deletes a single file, directory, or wildcard.
1222 * If /S was given, does it recursively.
1223 * Returns TRUE if a file was deleted.
1225 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1226 DWORD wanted_attrs;
1227 DWORD unwanted_attrs;
1228 BOOL found = FALSE;
1229 WCHAR argCopy[MAX_PATH];
1230 WIN32_FIND_DATAW fd;
1231 HANDLE hff;
1232 WCHAR fpath[MAX_PATH];
1233 WCHAR *p;
1234 BOOL handleParm = TRUE;
1236 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1238 lstrcpyW(argCopy, thisArg);
1239 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1240 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1242 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1243 /* Skip this arg if user declines to delete *.* */
1244 return FALSE;
1247 /* First, try to delete in the current directory */
1248 hff = FindFirstFileW(argCopy, &fd);
1249 if (hff == INVALID_HANDLE_VALUE) {
1250 handleParm = FALSE;
1251 } else {
1252 found = TRUE;
1255 /* Support del <dirname> by just deleting all files dirname\* */
1256 if (handleParm
1257 && (wcschr(argCopy,'*') == NULL)
1258 && (wcschr(argCopy,'?') == NULL)
1259 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1261 WCHAR modifiedParm[MAX_PATH];
1263 lstrcpyW(modifiedParm, argCopy);
1264 lstrcatW(modifiedParm, L"\\*");
1265 FindClose(hff);
1266 found = TRUE;
1267 WCMD_delete_one(modifiedParm);
1269 } else if (handleParm) {
1271 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1272 lstrcpyW (fpath, argCopy);
1273 do {
1274 p = wcsrchr (fpath, '\\');
1275 if (p != NULL) {
1276 *++p = '\0';
1277 lstrcatW (fpath, fd.cFileName);
1279 else lstrcpyW (fpath, fd.cFileName);
1280 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1281 BOOL ok;
1283 /* Handle attribute matching (/A) */
1284 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1285 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1287 /* /P means prompt for each file */
1288 if (ok && wcsstr(quals, L"/P") != NULL) {
1289 WCHAR* question;
1291 /* Ask for confirmation */
1292 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1293 ok = WCMD_ask_confirm(question, FALSE, NULL);
1294 LocalFree(question);
1297 /* Only proceed if ok to */
1298 if (ok) {
1300 /* If file is read only, and /A:r or /F supplied, delete it */
1301 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1302 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1303 wcsstr(quals, L"/F") != NULL)) {
1304 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1307 /* Now do the delete */
1308 if (!DeleteFileW(fpath)) WCMD_print_error ();
1312 } while (FindNextFileW(hff, &fd) != 0);
1313 FindClose (hff);
1316 /* Now recurse into all subdirectories handling the parameter in the same way */
1317 if (wcsstr(quals, L"/S") != NULL) {
1319 WCHAR thisDir[MAX_PATH];
1320 int cPos;
1322 WCHAR drive[10];
1323 WCHAR dir[MAX_PATH];
1324 WCHAR fname[MAX_PATH];
1325 WCHAR ext[MAX_PATH];
1327 /* Convert path into actual directory spec */
1328 if (!WCMD_get_fullpath(argCopy, ARRAY_SIZE(thisDir), thisDir, NULL)) return FALSE;
1330 _wsplitpath(thisDir, drive, dir, fname, ext);
1332 lstrcpyW(thisDir, drive);
1333 lstrcatW(thisDir, dir);
1334 cPos = lstrlenW(thisDir);
1336 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1338 /* Append '*' to the directory */
1339 thisDir[cPos] = '*';
1340 thisDir[cPos+1] = 0x00;
1342 hff = FindFirstFileW(thisDir, &fd);
1344 /* Remove residual '*' */
1345 thisDir[cPos] = 0x00;
1347 if (hff != INVALID_HANDLE_VALUE) {
1348 DIRECTORY_STACK *allDirs = NULL;
1349 DIRECTORY_STACK *lastEntry = NULL;
1351 do {
1352 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1353 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0)) {
1355 DIRECTORY_STACK *nextDir;
1356 WCHAR subParm[MAX_PATH];
1358 if (wcslen(thisDir) + wcslen(fd.cFileName) + 1 + wcslen(fname) + wcslen(ext) >= MAX_PATH)
1360 WINE_TRACE("Skipping path too long %s%s\\%s%s\n",
1361 debugstr_w(thisDir), debugstr_w(fd.cFileName),
1362 debugstr_w(fname), debugstr_w(ext));
1363 continue;
1365 /* Work out search parameter in sub dir */
1366 lstrcpyW (subParm, thisDir);
1367 lstrcatW (subParm, fd.cFileName);
1368 lstrcatW (subParm, L"\\");
1369 lstrcatW (subParm, fname);
1370 lstrcatW (subParm, ext);
1371 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1373 /* Allocate memory, add to list */
1374 nextDir = heap_xalloc(sizeof(DIRECTORY_STACK));
1375 if (allDirs == NULL) allDirs = nextDir;
1376 if (lastEntry != NULL) lastEntry->next = nextDir;
1377 lastEntry = nextDir;
1378 nextDir->next = NULL;
1379 nextDir->dirName = heap_strdupW(subParm);
1381 } while (FindNextFileW(hff, &fd) != 0);
1382 FindClose (hff);
1384 /* Go through each subdir doing the delete */
1385 while (allDirs != NULL) {
1386 DIRECTORY_STACK *tempDir;
1388 tempDir = allDirs->next;
1389 found |= WCMD_delete_one (allDirs->dirName);
1391 heap_free(allDirs->dirName);
1392 heap_free(allDirs);
1393 allDirs = tempDir;
1398 return found;
1401 /****************************************************************************
1402 * WCMD_delete
1404 * Delete a file or wildcarded set.
1406 * Note on /A:
1407 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1408 * - Each set is a pattern, eg /ahr /as-r means
1409 * readonly+hidden OR nonreadonly system files
1410 * - The '-' applies to a single field, ie /a:-hr means read only
1411 * non-hidden files
1414 BOOL WCMD_delete (WCHAR *args) {
1415 int argno;
1416 WCHAR *argN;
1417 BOOL argsProcessed = FALSE;
1418 BOOL foundAny = FALSE;
1420 errorlevel = 0;
1422 for (argno=0; ; argno++) {
1423 BOOL found;
1424 WCHAR *thisArg;
1426 argN = NULL;
1427 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1428 if (!argN)
1429 break; /* no more parameters */
1430 if (argN[0] == '/')
1431 continue; /* skip options */
1433 argsProcessed = TRUE;
1434 found = WCMD_delete_one(thisArg);
1435 if (!found)
1436 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1437 foundAny |= found;
1440 /* Handle no valid args */
1441 if (!argsProcessed)
1442 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1444 return foundAny;
1448 * WCMD_strtrim
1450 * Returns a trimmed version of s with all leading and trailing whitespace removed
1451 * Pre: s non NULL
1454 static WCHAR *WCMD_strtrim(const WCHAR *s)
1456 DWORD len = lstrlenW(s);
1457 const WCHAR *start = s;
1458 WCHAR* result;
1460 result = heap_xalloc((len + 1) * sizeof(WCHAR));
1462 while (iswspace(*start)) start++;
1463 if (*start) {
1464 const WCHAR *end = s + len - 1;
1465 while (end > start && iswspace(*end)) end--;
1466 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1467 result[end - start + 1] = '\0';
1468 } else {
1469 result[0] = '\0';
1472 return result;
1475 /****************************************************************************
1476 * WCMD_echo
1478 * Echo input to the screen (or not). We don't try to emulate the bugs
1479 * in DOS (try typing "ECHO ON AGAIN" for an example).
1482 void WCMD_echo (const WCHAR *args)
1484 int count;
1485 const WCHAR *origcommand = args;
1486 WCHAR *trimmed;
1488 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1489 || args[0]==':' || args[0]==';' || args[0]=='/')
1490 args++;
1492 trimmed = WCMD_strtrim(args);
1493 if (!trimmed) return;
1495 count = lstrlenW(trimmed);
1496 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1497 && origcommand[0]!=';' && origcommand[0]!='/') {
1498 if (echo_mode) WCMD_output(WCMD_LoadMessage(WCMD_ECHOPROMPT), L"ON");
1499 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), L"OFF");
1500 heap_free(trimmed);
1501 return;
1504 if (lstrcmpiW(trimmed, L"ON") == 0)
1505 echo_mode = TRUE;
1506 else if (lstrcmpiW(trimmed, L"OFF") == 0)
1507 echo_mode = FALSE;
1508 else {
1509 WCMD_output_asis (args);
1510 WCMD_output_asis(L"\r\n");
1512 heap_free(trimmed);
1515 /*****************************************************************************
1516 * WCMD_part_execute
1518 * Execute a command, and any && or bracketed follow on to the command. The
1519 * first command to be executed may not be at the front of the
1520 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1522 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1523 BOOL isIF, BOOL executecmds)
1525 CMD_LIST *curPosition = *cmdList;
1526 int myDepth = (*cmdList)->bracketDepth;
1528 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d), isIF(%d)\n", cmdList,
1529 wine_dbgstr_w(firstcmd), executecmds, isIF);
1531 /* Skip leading whitespace between condition and the command */
1532 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1534 /* Process the first command, if there is one */
1535 if (executecmds && firstcmd && *firstcmd) {
1536 WCHAR *command = heap_strdupW(firstcmd);
1537 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1538 heap_free(command);
1542 /* If it didn't move the position, step to next command */
1543 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1545 /* Process any other parts of the command */
1546 if (*cmdList) {
1547 BOOL processThese = executecmds;
1549 while (*cmdList) {
1550 /* execute all appropriate commands */
1551 curPosition = *cmdList;
1553 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d) processThese(%d)\n",
1554 *cmdList,
1555 (*cmdList)->prevDelim,
1556 (*cmdList)->bracketDepth,
1557 myDepth,
1558 processThese);
1560 /* Execute any statements appended to the line */
1561 /* FIXME: Only if previous call worked for && or failed for || */
1562 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1563 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1564 if (processThese && (*cmdList)->command) {
1565 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1566 cmdList, FALSE);
1568 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1570 /* Execute any appended to the statement with (...) */
1571 } else if ((*cmdList)->bracketDepth > myDepth) {
1572 if (processThese) {
1573 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1574 } else {
1575 WINE_TRACE("Skipping command %p due to stack depth\n", *cmdList);
1577 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1579 /* End of the command - does 'ELSE ' follow as the next command? */
1580 } else {
1581 if (isIF && WCMD_keyword_ws_found(L"else", (*cmdList)->command)) {
1582 /* Swap between if and else processing */
1583 processThese = !executecmds;
1585 /* Process the ELSE part */
1586 if (processThese) {
1587 const int keyw_len = lstrlenW(L"else") + 1;
1588 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1590 /* Skip leading whitespace between condition and the command */
1591 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1592 if (*cmd) {
1593 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1595 } else {
1596 /* Loop skipping all commands until we get back to the current
1597 depth, including skipping commands and their subsequent
1598 pipes (eg cmd | prog) */
1599 do {
1600 *cmdList = (*cmdList)->nextcommand;
1601 } while (*cmdList &&
1602 ((*cmdList)->bracketDepth > myDepth ||
1603 (*cmdList)->prevDelim));
1605 /* After the else is complete, we need to now process subsequent commands */
1606 processThese = TRUE;
1608 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1610 /* If we were in an IF statement and we didn't find an else and yet we get back to
1611 the same bracket depth as the IF, then the IF statement is over. This is required
1612 to handle nested ifs properly */
1613 } else if (isIF && (*cmdList)->bracketDepth == myDepth) {
1614 if (WCMD_keyword_ws_found(L"do", (*cmdList)->command)) {
1615 WINE_TRACE("Still inside FOR-loop, not an end of IF statement\n");
1616 *cmdList = (*cmdList)->nextcommand;
1617 } else {
1618 WINE_TRACE("Found end of this nested IF statement, ending this if\n");
1619 break;
1621 } else if (!processThese) {
1622 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1623 WINE_TRACE("Skipping this command, as in not process mode (next = %p)\n", *cmdList);
1624 } else {
1625 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1626 break;
1631 return;
1634 /*****************************************************************************
1635 * WCMD_parse_forf_options
1637 * Parses the for /f 'options', extracting the values and validating the
1638 * keywords. Note all keywords are optional.
1639 * Parameters:
1640 * options [I] The unparsed parameter string
1641 * eol [O] Set to the comment character (eol=x)
1642 * skip [O] Set to the number of lines to skip (skip=xx)
1643 * delims [O] Set to the token delimiters (delims=)
1644 * tokens [O] Set to the requested tokens, as provided (tokens=)
1645 * usebackq [O] Set to TRUE if usebackq found
1647 * Returns TRUE on success, FALSE on syntax error
1650 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1651 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1654 WCHAR *pos = options;
1655 int len = lstrlenW(pos);
1656 const int eol_len = lstrlenW(L"eol=");
1657 const int skip_len = lstrlenW(L"skip=");
1658 const int tokens_len = lstrlenW(L"tokens=");
1659 const int delims_len = lstrlenW(L"delims=");
1660 const int usebackq_len = lstrlenW(L"usebackq");
1662 /* Initialize to defaults */
1663 lstrcpyW(delims, L" \t");
1664 lstrcpyW(tokens, L"1");
1665 *eol = 0;
1666 *skip = 0;
1667 *usebackq = FALSE;
1669 /* Strip (optional) leading and trailing quotes */
1670 if ((*pos == '"') && (pos[len-1] == '"')) {
1671 pos[len-1] = 0;
1672 pos++;
1675 /* Process each keyword */
1676 while (pos && *pos) {
1677 if (*pos == ' ' || *pos == '\t') {
1678 pos++;
1680 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1681 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1682 pos, eol_len, L"eol=", eol_len) == CSTR_EQUAL) {
1683 *eol = *(pos + eol_len);
1684 pos = pos + eol_len + 1;
1685 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1687 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1688 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1689 pos, skip_len, L"skip=", skip_len) == CSTR_EQUAL) {
1690 WCHAR *nextchar = NULL;
1691 pos = pos + skip_len;
1692 *skip = wcstoul(pos, &nextchar, 0);
1693 WINE_TRACE("Found skip as %d lines\n", *skip);
1694 pos = nextchar;
1696 /* Save if usebackq semantics are in effect */
1697 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, pos,
1698 usebackq_len, L"usebackq", usebackq_len) == CSTR_EQUAL) {
1699 *usebackq = TRUE;
1700 pos = pos + usebackq_len;
1701 WINE_TRACE("Found usebackq\n");
1703 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1704 if you finish the optionsroot string with delims= otherwise the space is
1705 just a token delimiter! */
1706 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1707 pos, delims_len, L"delims=", delims_len) == CSTR_EQUAL) {
1708 int i=0;
1710 pos = pos + delims_len;
1711 while (*pos && *pos != ' ') {
1712 delims[i++] = *pos;
1713 pos++;
1715 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1716 delims[i++] = 0; /* Null terminate the delims */
1717 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1719 /* Save the tokens being requested */
1720 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1721 pos, tokens_len, L"tokens=", tokens_len) == CSTR_EQUAL) {
1722 int i=0;
1724 pos = pos + tokens_len;
1725 while (*pos && *pos != ' ') {
1726 tokens[i++] = *pos;
1727 pos++;
1729 tokens[i++] = 0; /* Null terminate the tokens */
1730 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1732 } else {
1733 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1734 return FALSE;
1737 return TRUE;
1740 /*****************************************************************************
1741 * WCMD_add_dirstowalk
1743 * When recursing through directories (for /r), we need to add to the list of
1744 * directories still to walk, any subdirectories of the one we are processing.
1746 * Parameters
1747 * options [I] The remaining list of directories still to process
1749 * Note this routine inserts the subdirectories found between the entry being
1750 * processed, and any other directory still to be processed, mimicking what
1751 * Windows does
1753 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1754 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1755 WCHAR fullitem[MAX_PATH];
1756 WIN32_FIND_DATAW fd;
1757 HANDLE hff;
1759 /* Build a generic search and add all directories on the list of directories
1760 still to walk */
1761 lstrcpyW(fullitem, dirsToWalk->dirName);
1762 lstrcatW(fullitem, L"\\*");
1763 hff = FindFirstFileW(fullitem, &fd);
1764 if (hff != INVALID_HANDLE_VALUE) {
1765 do {
1766 WINE_TRACE("Looking for subdirectories\n");
1767 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1768 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
1770 /* Allocate memory, add to list */
1771 DIRECTORY_STACK *toWalk;
1772 if (wcslen(dirsToWalk->dirName) + 1 + wcslen(fd.cFileName) >= MAX_PATH)
1774 WINE_TRACE("Skipping too long path %s\\%s\n",
1775 debugstr_w(dirsToWalk->dirName), debugstr_w(fd.cFileName));
1776 continue;
1778 toWalk = heap_xalloc(sizeof(DIRECTORY_STACK));
1779 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1780 toWalk->next = remainingDirs->next;
1781 remainingDirs->next = toWalk;
1782 remainingDirs = toWalk;
1783 toWalk->dirName = heap_xalloc(sizeof(WCHAR) * (lstrlenW(dirsToWalk->dirName) + 2 + lstrlenW(fd.cFileName)));
1784 lstrcpyW(toWalk->dirName, dirsToWalk->dirName);
1785 lstrcatW(toWalk->dirName, L"\\");
1786 lstrcatW(toWalk->dirName, fd.cFileName);
1787 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1788 toWalk, toWalk->next);
1790 } while (FindNextFileW(hff, &fd) != 0);
1791 WINE_TRACE("Finished adding all subdirectories\n");
1792 FindClose (hff);
1796 /**************************************************************************
1797 * WCMD_for_nexttoken
1799 * Parse the token= line, identifying the next highest number not processed
1800 * so far. Count how many tokens are referred (including duplicates) and
1801 * optionally return that, plus optionally indicate if the tokens= line
1802 * ends in a star.
1804 * Parameters:
1805 * lasttoken [I] - Identifies the token index of the last one
1806 * returned so far (-1 used for first loop)
1807 * tokenstr [I] - The specified tokens= line
1808 * firstCmd [O] - Optionally indicate how many tokens are listed
1809 * doAll [O] - Optionally indicate if line ends with *
1810 * duplicates [O] - Optionally indicate if there is any evidence of
1811 * overlaying tokens in the string
1812 * Note the caller should keep a running track of duplicates as the tokens
1813 * are recursively passed. If any have duplicates, then the * token should
1814 * not be honoured.
1816 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1817 int *totalfound, BOOL *doall,
1818 BOOL *duplicates)
1820 WCHAR *pos = tokenstr;
1821 int nexttoken = -1;
1823 if (totalfound) *totalfound = 0;
1824 if (doall) *doall = FALSE;
1825 if (duplicates) *duplicates = FALSE;
1827 WINE_TRACE("Find next token after %d in %s\n", lasttoken,
1828 wine_dbgstr_w(tokenstr));
1830 /* Loop through the token string, parsing it. Valid syntax is:
1831 token=m or x-y with comma delimiter and optionally * to finish*/
1832 while (*pos) {
1833 int nextnumber1, nextnumber2 = -1;
1834 WCHAR *nextchar;
1836 /* Remember if the next character is a star, it indicates a need to
1837 show all remaining tokens and should be the last character */
1838 if (*pos == '*') {
1839 if (doall) *doall = TRUE;
1840 if (totalfound) (*totalfound)++;
1841 /* If we have not found a next token to return, then indicate
1842 time to process the star */
1843 if (nexttoken == -1) {
1844 if (lasttoken == -1) {
1845 /* Special case the syntax of tokens=* which just means get whole line */
1846 nexttoken = 0;
1847 } else {
1848 nexttoken = lasttoken;
1851 break;
1854 /* Get the next number */
1855 nextnumber1 = wcstoul(pos, &nextchar, 10);
1857 /* If it is followed by a minus, it's a range, so get the next one as well */
1858 if (*nextchar == '-') {
1859 nextnumber2 = wcstoul(nextchar+1, &nextchar, 10);
1861 /* We want to return the lowest number that is higher than lasttoken
1862 but only if range is positive */
1863 if (nextnumber2 >= nextnumber1 &&
1864 lasttoken < nextnumber2) {
1866 int nextvalue;
1867 if (nexttoken == -1) {
1868 nextvalue = max(nextnumber1, (lasttoken+1));
1869 } else {
1870 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1873 /* Flag if duplicates identified */
1874 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1876 nexttoken = nextvalue;
1879 /* Update the running total for the whole range */
1880 if (nextnumber2 >= nextnumber1 && totalfound) {
1881 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1883 pos = nextchar;
1885 } else if (pos != nextchar) {
1886 if (totalfound) (*totalfound)++;
1888 /* See if the number found is one we have already seen */
1889 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1891 /* We want to return the lowest number that is higher than lasttoken */
1892 if (lasttoken < nextnumber1 &&
1893 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1894 nexttoken = nextnumber1;
1896 pos = nextchar;
1898 } else {
1899 /* Step on to the next character, usually over comma */
1900 if (*pos) pos++;
1905 /* Return result */
1906 if (nexttoken == -1) {
1907 WINE_TRACE("No next token found, previous was %d\n", lasttoken);
1908 nexttoken = lasttoken;
1909 } else if (nexttoken==lasttoken && doall && *doall) {
1910 WINE_TRACE("Request for all remaining tokens now\n");
1911 } else {
1912 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1914 if (totalfound) WINE_TRACE("Found total tokens to be %d\n", *totalfound);
1915 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1916 return nexttoken;
1919 /**************************************************************************
1920 * WCMD_parse_line
1922 * When parsing file or string contents (for /f), once the string to parse
1923 * has been identified, handle the various options and call the do part
1924 * if appropriate.
1926 * Parameters:
1927 * cmdStart [I] - Identifies the list of commands making up the
1928 * for loop body (especially if brackets in use)
1929 * firstCmd [I] - The textual start of the command after the DO
1930 * which is within the first item of cmdStart
1931 * cmdEnd [O] - Identifies where to continue after the DO
1932 * variable [I] - The variable identified on the for line
1933 * buffer [I] - The string to parse
1934 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1935 * forf_skip [I/O] - How many lines to skip first
1936 * forf_eol [I] - The 'end of line' (comment) character
1937 * forf_delims [I] - The delimiters to use when breaking the string apart
1938 * forf_tokens [I] - The tokens to use when breaking the string apart
1940 static void WCMD_parse_line(CMD_LIST *cmdStart,
1941 const WCHAR *firstCmd,
1942 CMD_LIST **cmdEnd,
1943 const WCHAR variable,
1944 WCHAR *buffer,
1945 BOOL *doExecuted,
1946 int *forf_skip,
1947 WCHAR forf_eol,
1948 WCHAR *forf_delims,
1949 WCHAR *forf_tokens) {
1951 WCHAR *parm;
1952 FOR_CONTEXT oldcontext;
1953 int varidx, varoffset;
1954 int nexttoken, lasttoken = -1;
1955 BOOL starfound = FALSE;
1956 BOOL thisduplicate = FALSE;
1957 BOOL anyduplicates = FALSE;
1958 int totalfound;
1959 static WCHAR emptyW[] = L"";
1961 /* Skip lines if requested */
1962 if (*forf_skip) {
1963 (*forf_skip)--;
1964 return;
1967 /* Save away any existing for variable context (e.g. nested for loops) */
1968 oldcontext = forloopcontext;
1970 /* Extract the parameters based on the tokens= value (There will always
1971 be some value, as if it is not supplied, it defaults to tokens=1).
1972 Rough logic:
1973 Count how many tokens are named in the line, identify the lowest
1974 Empty (set to null terminated string) that number of named variables
1975 While lasttoken != nextlowest
1976 %letter = parameter number 'nextlowest'
1977 letter++ (if >26 or >52 abort)
1978 Go through token= string finding next lowest number
1979 If token ends in * set %letter = raw position of token(nextnumber+1)
1981 lasttoken = -1;
1982 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1983 &starfound, &thisduplicate);
1984 varidx = FOR_VAR_IDX(variable);
1986 /* Empty out variables */
1987 for (varoffset=0;
1988 varidx >= 0 && varoffset<totalfound && (((varidx%26) + varoffset) < 26);
1989 varoffset++) {
1990 forloopcontext.variable[varidx + varoffset] = emptyW;
1993 /* Loop extracting the tokens
1994 Note: nexttoken of 0 means there were no tokens requested, to handle
1995 the special case of tokens=* */
1996 varoffset = 0;
1997 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1998 while (varidx >= 0 && (nexttoken > 0 && (nexttoken > lasttoken))) {
1999 anyduplicates |= thisduplicate;
2001 /* Extract the token number requested and set into the next variable context */
2002 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims);
2003 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
2004 varidx + varoffset, wine_dbgstr_w(parm));
2005 if (varidx >=0) {
2006 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2007 varoffset++;
2008 if (((varidx%26)+varoffset) >= 26) break;
2011 /* Find the next token */
2012 lasttoken = nexttoken;
2013 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
2014 &starfound, &thisduplicate);
2017 /* If all the rest of the tokens were requested, and there is still space in
2018 the variable range, write them now */
2019 if (!anyduplicates && starfound && varidx >= 0 && (((varidx%26) + varoffset) < 26)) {
2020 nexttoken++;
2021 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
2022 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2023 varidx + varoffset, wine_dbgstr_w(parm));
2024 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2027 /* Execute the body of the foor loop with these values */
2028 if (varidx >= 0 && forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
2029 CMD_LIST *thisCmdStart = cmdStart;
2030 *doExecuted = TRUE;
2031 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
2032 *cmdEnd = thisCmdStart;
2035 /* Free the duplicated strings, and restore the context */
2036 if (varidx >=0) {
2037 int i;
2038 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
2039 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
2040 (forloopcontext.variable[i] != emptyW)) {
2041 heap_free(forloopcontext.variable[i]);
2046 /* Restore the original for variable contextx */
2047 forloopcontext = oldcontext;
2050 /**************************************************************************
2051 * WCMD_forf_getinput
2053 * Return a FILE* which can be used for reading the input lines,
2054 * either to a specific file (which may be quote delimited as we have to
2055 * read the parameters in raw mode) or to a command which we need to
2056 * execute. The command being executed runs in its own shell and stores
2057 * its data in a temporary file.
2059 * Parameters:
2060 * usebackq [I] - Indicates whether usebackq is in effect or not
2061 * itemStr [I] - The item to be handled, either a filename or
2062 * whole command string to execute
2063 * iscmd [I] - Identifies whether this is a command or not
2065 * Returns a file handle which can be used to read the input lines from.
2067 static FILE* WCMD_forf_getinput(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2068 WCHAR temp_str[MAX_PATH];
2069 WCHAR temp_file[MAX_PATH];
2070 WCHAR temp_cmd[MAXSTRING];
2071 WCHAR *trimmed = NULL;
2072 FILE* ret;
2074 /* Remove leading and trailing character (but there may be trailing whitespace too) */
2075 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2076 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2077 (!iscmd && (itemstr[0] == '"' && usebackq)))
2079 trimmed = WCMD_strtrim(itemstr);
2080 if (trimmed) {
2081 itemstr = trimmed;
2083 itemstr[lstrlenW(itemstr)-1] = 0x00;
2084 itemstr++;
2087 if (iscmd) {
2088 /* Get temp filename */
2089 GetTempPathW(ARRAY_SIZE(temp_str), temp_str);
2090 GetTempFileNameW(temp_str, L"CMD", 0, temp_file);
2092 /* Redirect output to the temporary file */
2093 wsprintfW(temp_str, L">%s", temp_file);
2094 wsprintfW(temp_cmd, L"CMD.EXE /C %s", itemstr);
2095 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2096 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2097 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2099 /* Open the file, read line by line and process */
2100 ret = _wfopen(temp_file, L"rt,ccs=unicode");
2101 } else {
2102 /* Open the file, read line by line and process */
2103 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2104 ret = _wfopen(itemstr, L"rt,ccs=unicode");
2106 heap_free(trimmed);
2107 return ret;
2110 /**************************************************************************
2111 * WCMD_for
2113 * Batch file loop processing.
2115 * On entry: cmdList contains the syntax up to the set
2116 * next cmdList and all in that bracket contain the set data
2117 * next cmdlist contains the DO cmd
2118 * following that is either brackets or && entries (as per if)
2122 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2124 WIN32_FIND_DATAW fd;
2125 HANDLE hff;
2126 int i;
2127 const int in_len = lstrlenW(L"in");
2128 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2129 WCHAR variable[4];
2130 int varidx = -1;
2131 WCHAR *oldvariablevalue;
2132 WCHAR *firstCmd;
2133 int thisDepth;
2134 WCHAR optionsRoot[MAX_PATH];
2135 DIRECTORY_STACK *dirsToWalk = NULL;
2136 BOOL expandDirs = FALSE;
2137 BOOL useNumbers = FALSE;
2138 BOOL doFileset = FALSE;
2139 BOOL doRecurse = FALSE;
2140 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2141 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2142 int itemNum;
2143 CMD_LIST *thisCmdStart;
2144 int parameterNo = 0;
2145 WCHAR forf_eol = 0;
2146 int forf_skip = 0;
2147 WCHAR forf_delims[256];
2148 WCHAR forf_tokens[MAXSTRING];
2149 BOOL forf_usebackq = FALSE;
2151 /* Handle optional qualifiers (multiple are allowed) */
2152 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2154 optionsRoot[0] = 0;
2155 while (thisArg && *thisArg == '/') {
2156 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2157 thisArg++;
2158 switch (towupper(*thisArg)) {
2159 case 'D': expandDirs = TRUE; break;
2160 case 'L': useNumbers = TRUE; break;
2162 /* Recursive is special case - /R can have an optional path following it */
2163 /* filenamesets are another special case - /F can have an optional options following it */
2164 case 'R':
2165 case 'F':
2167 /* When recursing directories, use current directory as the starting point unless
2168 subsequently overridden */
2169 doRecurse = (towupper(*thisArg) == 'R');
2170 if (doRecurse) GetCurrentDirectoryW(ARRAY_SIZE(optionsRoot), optionsRoot);
2172 doFileset = (towupper(*thisArg) == 'F');
2174 /* Retrieve next parameter to see if is root/options (raw form required
2175 with for /f, or unquoted in for /r) */
2176 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2178 /* Next parm is either qualifier, path/options or variable -
2179 only care about it if it is the path/options */
2180 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2181 parameterNo++;
2182 lstrcpyW(optionsRoot, thisArg);
2184 break;
2186 default:
2187 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2190 /* Step to next token */
2191 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2194 /* Ensure line continues with variable */
2195 if (*thisArg != '%') {
2196 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2197 return;
2200 /* With for /f parse the options if provided */
2201 if (doFileset) {
2202 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2203 forf_delims, forf_tokens, &forf_usebackq))
2205 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2206 return;
2209 /* Set up the list of directories to recurse if we are going to */
2210 } else if (doRecurse) {
2211 /* Allocate memory, add to list */
2212 dirsToWalk = heap_xalloc(sizeof(DIRECTORY_STACK));
2213 dirsToWalk->next = NULL;
2214 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2215 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2218 /* Variable should follow */
2219 lstrcpyW(variable, thisArg);
2220 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2221 varidx = FOR_VAR_IDX(variable[1]);
2223 /* Ensure line continues with IN */
2224 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2225 if (!thisArg
2226 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2227 thisArg, in_len, L"in", in_len) == CSTR_EQUAL)) {
2228 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2229 return;
2232 /* Save away where the set of data starts and the variable */
2233 thisDepth = (*cmdList)->bracketDepth;
2234 *cmdList = (*cmdList)->nextcommand;
2235 setStart = (*cmdList);
2237 /* Skip until the close bracket */
2238 WINE_TRACE("Searching %p as the set\n", *cmdList);
2239 while (*cmdList &&
2240 (*cmdList)->command != NULL &&
2241 (*cmdList)->bracketDepth > thisDepth) {
2242 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2243 *cmdList = (*cmdList)->nextcommand;
2246 /* Skip the close bracket, if there is one */
2247 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2249 /* Syntax error if missing close bracket, or nothing following it
2250 and once we have the complete set, we expect a DO */
2251 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2252 if ((*cmdList == NULL) || !WCMD_keyword_ws_found(L"do", (*cmdList)->command)) {
2253 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2254 return;
2257 cmdEnd = *cmdList;
2259 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2260 mode, or once for the rest of the time. */
2261 do {
2263 /* Save away the starting position for the commands (and offset for the
2264 first one) */
2265 cmdStart = *cmdList;
2266 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2267 itemNum = 0;
2269 /* If we are recursing directories (ie /R), add all sub directories now, then
2270 prefix the root when searching for the item */
2271 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2273 thisSet = setStart;
2274 /* Loop through all set entries */
2275 while (thisSet &&
2276 thisSet->command != NULL &&
2277 thisSet->bracketDepth >= thisDepth) {
2279 /* Loop through all entries on the same line */
2280 WCHAR *staticitem;
2281 WCHAR *itemStart;
2282 WCHAR buffer[MAXSTRING];
2284 WINE_TRACE("Processing for set %p\n", thisSet);
2285 i = 0;
2286 while (*(staticitem = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2289 * If the parameter within the set has a wildcard then search for matching files
2290 * otherwise do a literal substitution.
2293 /* Take a copy of the item returned from WCMD_parameter as it is held in a
2294 static buffer which can be overwritten during parsing of the for body */
2295 WCHAR item[MAXSTRING];
2296 lstrcpyW(item, staticitem);
2298 thisCmdStart = cmdStart;
2300 itemNum++;
2301 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2303 if (!useNumbers && !doFileset) {
2304 WCHAR fullitem[MAX_PATH];
2305 int prefixlen = 0;
2307 /* Now build the item to use / search for in the specified directory,
2308 as it is fully qualified in the /R case */
2309 if (dirsToWalk) {
2310 lstrcpyW(fullitem, dirsToWalk->dirName);
2311 lstrcatW(fullitem, L"\\");
2312 lstrcatW(fullitem, item);
2313 } else {
2314 WCHAR *prefix = wcsrchr(item, '\\');
2315 if (prefix) prefixlen = (prefix - item) + 1;
2316 lstrcpyW(fullitem, item);
2319 if (wcspbrk(fullitem, L"*?")) {
2320 hff = FindFirstFileW(fullitem, &fd);
2321 if (hff != INVALID_HANDLE_VALUE) {
2322 do {
2323 BOOL isDirectory = FALSE;
2325 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2327 /* Handle as files or dirs appropriately, but ignore . and .. */
2328 if (isDirectory == expandDirs &&
2329 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
2331 thisCmdStart = cmdStart;
2332 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2334 if (doRecurse) {
2335 if (wcslen(dirsToWalk->dirName) + 1 + wcslen(fd.cFileName) >= MAX_PATH)
2337 WINE_TRACE("Skipping too long path %s\\%s\n",
2338 debugstr_w(dirsToWalk->dirName), debugstr_w(fd.cFileName));
2339 continue;
2341 lstrcpyW(fullitem, dirsToWalk->dirName);
2342 lstrcatW(fullitem, L"\\");
2343 lstrcatW(fullitem, fd.cFileName);
2344 } else {
2345 if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
2346 fullitem[prefixlen] = 0x00;
2347 lstrcatW(fullitem, fd.cFileName);
2349 doExecuted = TRUE;
2351 /* Save away any existing for variable context (e.g. nested for loops)
2352 and restore it after executing the body of this for loop */
2353 if (varidx >= 0) {
2354 oldvariablevalue = forloopcontext.variable[varidx];
2355 forloopcontext.variable[varidx] = fullitem;
2357 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2358 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2360 cmdEnd = thisCmdStart;
2362 } while (FindNextFileW(hff, &fd) != 0);
2363 FindClose (hff);
2365 } else {
2366 doExecuted = TRUE;
2368 /* Save away any existing for variable context (e.g. nested for loops)
2369 and restore it after executing the body of this for loop */
2370 if (varidx >= 0) {
2371 oldvariablevalue = forloopcontext.variable[varidx];
2372 forloopcontext.variable[varidx] = fullitem;
2374 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2375 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2377 cmdEnd = thisCmdStart;
2380 } else if (useNumbers) {
2381 /* Convert the first 3 numbers to signed longs and save */
2382 if (itemNum <=3) numbers[itemNum-1] = wcstol(item, NULL, 10);
2383 /* else ignore them! */
2385 /* Filesets - either a list of files, or a command to run and parse the output */
2386 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2387 (forf_usebackq && *itemStart != '\''))) {
2389 FILE *input;
2390 WCHAR *itemparm;
2392 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2393 wine_dbgstr_w(item));
2395 /* If backquote or single quote, we need to launch that command
2396 and parse the results - use a temporary file */
2397 if ((forf_usebackq && *itemStart == '`') ||
2398 (!forf_usebackq && *itemStart == '\'')) {
2400 /* Use itemstart because the command is the whole set, not just the first token */
2401 itemparm = itemStart;
2402 } else {
2404 /* Use item because the file to process is just the first item in the set */
2405 itemparm = item;
2407 input = WCMD_forf_getinput(forf_usebackq, itemparm, (itemparm==itemStart));
2409 /* Process the input file */
2410 if (!input) {
2411 WCMD_print_error ();
2412 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2413 errorlevel = 1;
2414 return; /* FOR loop aborts at first failure here */
2416 } else {
2418 /* Read line by line until end of file */
2419 while (fgetws(buffer, ARRAY_SIZE(buffer), input)) {
2420 size_t len = wcslen(buffer);
2421 /* Either our buffer isn't large enough to fit a full line, or there's a stray
2422 * '\0' in the buffer.
2424 if (!feof(input) && (len == 0 || (buffer[len - 1] != '\n' && buffer[len - 1] != '\r')))
2425 break;
2426 while (len && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r'))
2427 buffer[--len] = L'\0';
2428 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2429 &forf_skip, forf_eol, forf_delims, forf_tokens);
2430 buffer[0] = 0;
2432 fclose (input);
2435 /* When we have processed the item as a whole command, abort future set processing */
2436 if (itemparm==itemStart) {
2437 thisSet = NULL;
2438 break;
2441 /* Filesets - A string literal */
2442 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2443 (forf_usebackq && *itemStart == '\''))) {
2445 /* Remove leading and trailing character, ready to parse with delims= delimiters
2446 Note that the last quote is removed from the set and the string terminates
2447 there to mimic windows */
2448 WCHAR *strend = wcsrchr(itemStart, forf_usebackq?'\'':'"');
2449 if (strend) {
2450 *strend = 0x00;
2451 itemStart++;
2454 /* Copy the item away from the global buffer used by WCMD_parameter */
2455 lstrcpyW(buffer, itemStart);
2456 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2457 &forf_skip, forf_eol, forf_delims, forf_tokens);
2459 /* Only one string can be supplied in the whole set, abort future set processing */
2460 thisSet = NULL;
2461 break;
2464 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2465 i++;
2468 /* Move onto the next set line */
2469 if (thisSet) thisSet = thisSet->nextcommand;
2472 /* If /L is provided, now run the for loop */
2473 if (useNumbers) {
2474 WCHAR thisNum[20];
2476 WINE_TRACE("FOR /L provided range from %ld to %ld step %ld\n",
2477 numbers[0], numbers[2], numbers[1]);
2478 for (i=numbers[0];
2479 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2480 i=i + numbers[1]) {
2482 swprintf(thisNum, ARRAY_SIZE(thisNum), L"%d", i);
2483 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2485 thisCmdStart = cmdStart;
2486 doExecuted = TRUE;
2488 /* Save away any existing for variable context (e.g. nested for loops)
2489 and restore it after executing the body of this for loop */
2490 if (varidx >= 0) {
2491 oldvariablevalue = forloopcontext.variable[varidx];
2492 forloopcontext.variable[varidx] = thisNum;
2494 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2495 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2497 cmdEnd = thisCmdStart;
2500 /* If we are walking directories, move on to any which remain */
2501 if (dirsToWalk != NULL) {
2502 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2503 heap_free(dirsToWalk->dirName);
2504 heap_free(dirsToWalk);
2505 dirsToWalk = nextDir;
2506 if (dirsToWalk) WINE_TRACE("Moving to next directory to iterate: %s\n",
2507 wine_dbgstr_w(dirsToWalk->dirName));
2508 else WINE_TRACE("Finished all directories.\n");
2511 } while (dirsToWalk != NULL);
2513 /* Now skip over the do part if we did not perform the for loop so far.
2514 We store in cmdEnd the next command after the do block, but we only
2515 know this if something was run. If it has not been, we need to calculate
2516 it. */
2517 if (!doExecuted) {
2518 thisCmdStart = cmdStart;
2519 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2520 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2521 cmdEnd = thisCmdStart;
2524 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2525 all processing, OR it should be pointing to the end of && processing OR
2526 it should be pointing at the NULL end of bracket for the DO. The return
2527 value needs to be the NEXT command to execute, which it either is, or
2528 we need to step over the closing bracket */
2529 *cmdList = cmdEnd;
2530 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2533 /**************************************************************************
2534 * WCMD_give_help
2536 * Simple on-line help. Help text is stored in the resource file.
2539 void WCMD_give_help (const WCHAR *args)
2541 size_t i;
2543 args = WCMD_skip_leading_spaces((WCHAR*) args);
2544 if (!*args) {
2545 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2547 else {
2548 /* Display help message for builtin commands */
2549 for (i=0; i<=WCMD_EXIT; i++) {
2550 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2551 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2552 WCMD_output_asis (WCMD_LoadMessage(i));
2553 return;
2556 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2557 for (i = 0; i <= ARRAY_SIZE(externals); i++) {
2558 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2559 args, -1, externals[i], -1) == CSTR_EQUAL) {
2560 WCHAR cmd[128];
2561 lstrcpyW(cmd, args);
2562 lstrcatW(cmd, L" /?");
2563 WCMD_run_program(cmd, FALSE);
2564 return;
2567 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2569 return;
2572 /****************************************************************************
2573 * WCMD_go_to
2575 * Batch file jump instruction. Not the most efficient algorithm ;-)
2576 * Prints error message if the specified label cannot be found - the file pointer is
2577 * then at EOF, effectively stopping the batch file.
2578 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2581 void WCMD_goto (CMD_LIST **cmdList) {
2583 WCHAR string[MAX_PATH];
2584 WCHAR *labelend = NULL;
2585 const WCHAR labelEndsW[] = L"><|& :\t";
2587 /* Do not process any more parts of a processed multipart or multilines command */
2588 if (cmdList) *cmdList = NULL;
2590 if (context != NULL) {
2591 WCHAR *paramStart = param1, *str;
2593 if (param1[0] == 0x00) {
2594 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2595 return;
2598 /* Handle special :EOF label */
2599 if (lstrcmpiW(L":eof", param1) == 0) {
2600 context -> skip_rest = TRUE;
2601 return;
2604 /* Support goto :label as well as goto label plus remove trailing chars */
2605 if (*paramStart == ':') paramStart++;
2606 labelend = wcspbrk(paramStart, labelEndsW);
2607 if (labelend) *labelend = 0x00;
2608 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
2610 /* Loop through potentially twice - once from current file position
2611 through to the end, and second time from start to current file
2612 position */
2613 if (*paramStart) {
2614 int loop;
2615 LARGE_INTEGER startli;
2616 for (loop=0; loop<2; loop++) {
2617 if (loop==0) {
2618 /* On first loop, save the file size */
2619 startli.QuadPart = 0;
2620 startli.u.LowPart = SetFilePointer(context -> h, startli.u.LowPart,
2621 &startli.u.HighPart, FILE_CURRENT);
2622 } else {
2623 /* On second loop, start at the beginning of the file */
2624 WINE_TRACE("Label not found, trying from beginning of file\n");
2625 if (loop==1) SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2628 while (WCMD_fgets (string, ARRAY_SIZE(string), context -> h)) {
2629 str = string;
2631 /* Ignore leading whitespace or no-echo character */
2632 while (*str=='@' || iswspace (*str)) str++;
2634 /* If the first real character is a : then this is a label */
2635 if (*str == ':') {
2636 str++;
2638 /* Skip spaces between : and label */
2639 while (iswspace (*str)) str++;
2640 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
2642 /* Label ends at whitespace or redirection characters */
2643 labelend = wcspbrk(str, labelEndsW);
2644 if (labelend) *labelend = 0x00;
2645 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
2647 if (lstrcmpiW (str, paramStart) == 0) return;
2650 /* See if we have gone beyond the end point if second time through */
2651 if (loop==1) {
2652 LARGE_INTEGER curli;
2653 curli.QuadPart = 0;
2654 curli.u.LowPart = SetFilePointer(context -> h, curli.u.LowPart,
2655 &curli.u.HighPart, FILE_CURRENT);
2656 if (curli.QuadPart > startli.QuadPart) {
2657 WINE_TRACE("Reached wrap point, label not found\n");
2658 break;
2665 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2666 context -> skip_rest = TRUE;
2668 return;
2671 /*****************************************************************************
2672 * WCMD_pushd
2674 * Push a directory onto the stack
2677 void WCMD_pushd (const WCHAR *args)
2679 struct env_stack *curdir;
2680 WCHAR *thisdir;
2682 if (wcschr(args, '/') != NULL) {
2683 SetLastError(ERROR_INVALID_PARAMETER);
2684 WCMD_print_error();
2685 return;
2688 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2689 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2690 if( !curdir || !thisdir ) {
2691 LocalFree(curdir);
2692 LocalFree(thisdir);
2693 WINE_ERR ("out of memory\n");
2694 return;
2697 /* Change directory using CD code with /D parameter */
2698 lstrcpyW(quals, L"/D");
2699 GetCurrentDirectoryW (1024, thisdir);
2700 errorlevel = 0;
2701 WCMD_setshow_default(args);
2702 if (errorlevel) {
2703 LocalFree(curdir);
2704 LocalFree(thisdir);
2705 return;
2706 } else {
2707 curdir -> next = pushd_directories;
2708 curdir -> strings = thisdir;
2709 if (pushd_directories == NULL) {
2710 curdir -> u.stackdepth = 1;
2711 } else {
2712 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2714 pushd_directories = curdir;
2719 /*****************************************************************************
2720 * WCMD_popd
2722 * Pop a directory from the stack
2725 void WCMD_popd (void) {
2726 struct env_stack *temp = pushd_directories;
2728 if (!pushd_directories)
2729 return;
2731 /* pop the old environment from the stack, and make it the current dir */
2732 pushd_directories = temp->next;
2733 SetCurrentDirectoryW(temp->strings);
2734 LocalFree (temp->strings);
2735 LocalFree (temp);
2738 /*******************************************************************
2739 * evaluate_if_comparison
2741 * Evaluates an "if" comparison operation
2743 * PARAMS
2744 * leftOperand [I] left operand, non NULL
2745 * operator [I] "if" binary comparison operator, non NULL
2746 * rightOperand [I] right operand, non NULL
2747 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2749 * RETURNS
2750 * Success: 1 if operator applied to the operands evaluates to TRUE
2751 * 0 if operator applied to the operands evaluates to FALSE
2752 * Failure: -1 if operator is not recognized
2754 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2755 const WCHAR *rightOperand, int caseInsensitive)
2757 WCHAR *endptr_leftOp, *endptr_rightOp;
2758 long int leftOperand_int, rightOperand_int;
2759 BOOL int_operands;
2761 /* == is a special case, as it always compares strings */
2762 if (!lstrcmpiW(operator, L"=="))
2763 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2764 : lstrcmpW (leftOperand, rightOperand) == 0;
2766 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2767 leftOperand_int = wcstol(leftOperand, &endptr_leftOp, 0);
2768 rightOperand_int = wcstol(rightOperand, &endptr_rightOp, 0);
2769 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2771 /* Perform actual (integer or string) comparison */
2772 if (!lstrcmpiW(operator, L"lss")) {
2773 if (int_operands)
2774 return leftOperand_int < rightOperand_int;
2775 else
2776 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2777 : lstrcmpW (leftOperand, rightOperand) < 0;
2780 if (!lstrcmpiW(operator, L"leq")) {
2781 if (int_operands)
2782 return leftOperand_int <= rightOperand_int;
2783 else
2784 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2785 : lstrcmpW (leftOperand, rightOperand) <= 0;
2788 if (!lstrcmpiW(operator, L"equ")) {
2789 if (int_operands)
2790 return leftOperand_int == rightOperand_int;
2791 else
2792 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2793 : lstrcmpW (leftOperand, rightOperand) == 0;
2796 if (!lstrcmpiW(operator, L"neq")) {
2797 if (int_operands)
2798 return leftOperand_int != rightOperand_int;
2799 else
2800 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2801 : lstrcmpW (leftOperand, rightOperand) != 0;
2804 if (!lstrcmpiW(operator, L"geq")) {
2805 if (int_operands)
2806 return leftOperand_int >= rightOperand_int;
2807 else
2808 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2809 : lstrcmpW (leftOperand, rightOperand) >= 0;
2812 if (!lstrcmpiW(operator, L"gtr")) {
2813 if (int_operands)
2814 return leftOperand_int > rightOperand_int;
2815 else
2816 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2817 : lstrcmpW (leftOperand, rightOperand) > 0;
2820 return -1;
2823 int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate)
2825 WCHAR condition[MAX_PATH];
2826 int caseInsensitive = (wcsstr(quals, L"/I") != NULL);
2828 *negate = !lstrcmpiW(param1,L"not");
2829 lstrcpyW(condition, (*negate ? param2 : param1));
2830 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2832 if (!lstrcmpiW(condition, L"errorlevel")) {
2833 WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
2834 WCHAR *endptr;
2835 long int param_int = wcstol(param, &endptr, 10);
2836 if (*endptr) goto syntax_err;
2837 *test = ((long int)errorlevel >= param_int);
2838 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2840 else if (!lstrcmpiW(condition, L"exist")) {
2841 WIN32_FIND_DATAW fd;
2842 HANDLE hff;
2843 WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
2844 int len = lstrlenW(param);
2846 if (!len) goto syntax_err;
2847 /* FindFirstFile does not like a directory path ending in '\', append a '.' */
2848 if (param[len-1] == '\\') lstrcatW(param, L".");
2850 hff = FindFirstFileW(param, &fd);
2851 *test = (hff != INVALID_HANDLE_VALUE );
2852 if (*test) FindClose(hff);
2854 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2856 else if (!lstrcmpiW(condition, L"defined")) {
2857 *test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE),
2858 NULL, 0) > 0);
2859 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2861 else { /* comparison operation */
2862 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2863 WCHAR *paramStart;
2865 lstrcpyW(leftOperand, WCMD_parameter(p, (*negate)+caseInsensitive, &paramStart, TRUE, FALSE));
2866 if (!*leftOperand)
2867 goto syntax_err;
2869 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2870 p = paramStart + lstrlenW(leftOperand);
2871 while (*p == ' ' || *p == '\t')
2872 p++;
2874 if (!wcsncmp(p, L"==", lstrlenW(L"==")))
2875 lstrcpyW(operator, L"==");
2876 else {
2877 lstrcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2878 if (!*operator) goto syntax_err;
2880 p += lstrlenW(operator);
2882 lstrcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2883 if (!*rightOperand)
2884 goto syntax_err;
2886 *test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2887 if (*test == -1)
2888 goto syntax_err;
2890 p = paramStart + lstrlenW(rightOperand);
2891 WCMD_parameter(p, 0, command, FALSE, FALSE);
2894 return 1;
2896 syntax_err:
2897 return -1;
2900 /****************************************************************************
2901 * WCMD_if
2903 * Batch file conditional.
2905 * On entry, cmdlist will point to command containing the IF, and optionally
2906 * the first command to execute (if brackets not found)
2907 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2908 * If ('s were found, execute all within that bracket
2909 * Command may optionally be followed by an ELSE - need to skip instructions
2910 * in the else using the same logic
2912 * FIXME: Much more syntax checking needed!
2914 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2916 int negate; /* Negate condition */
2917 int test; /* Condition evaluation result */
2918 WCHAR *command;
2920 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
2921 set in a call to WCMD_parse before */
2922 if (evaluate_if_condition(p, &command, &test, &negate) == -1)
2923 goto syntax_err;
2925 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n",
2926 wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
2927 wine_dbgstr_w(param2), wine_dbgstr_w(command));
2929 /* Process rest of IF statement which is on the same line
2930 Note: This may process all or some of the cmdList (eg a GOTO) */
2931 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2932 return;
2934 syntax_err:
2935 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2938 /****************************************************************************
2939 * WCMD_move
2941 * Move a file, directory tree or wildcarded set of files.
2944 void WCMD_move (void)
2946 BOOL status;
2947 WIN32_FIND_DATAW fd;
2948 HANDLE hff;
2949 WCHAR input[MAX_PATH];
2950 WCHAR output[MAX_PATH];
2951 WCHAR drive[10];
2952 WCHAR dir[MAX_PATH];
2953 WCHAR fname[MAX_PATH];
2954 WCHAR ext[MAX_PATH];
2956 if (param1[0] == 0x00) {
2957 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2958 return;
2961 /* If no destination supplied, assume current directory */
2962 if (param2[0] == 0x00) {
2963 lstrcpyW(param2, L".");
2966 /* If 2nd parm is directory, then use original filename */
2967 /* Convert partial path to full path */
2968 if (!WCMD_get_fullpath(param1, ARRAY_SIZE(input), input, NULL) ||
2969 !WCMD_get_fullpath(param2, ARRAY_SIZE(output), output, NULL)) return;
2970 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2971 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2973 /* Split into components */
2974 _wsplitpath(input, drive, dir, fname, ext);
2976 hff = FindFirstFileW(input, &fd);
2977 if (hff == INVALID_HANDLE_VALUE)
2978 return;
2980 do {
2981 WCHAR dest[MAX_PATH];
2982 WCHAR src[MAX_PATH];
2983 DWORD attribs;
2984 BOOL ok = TRUE;
2985 DWORD flags = 0;
2987 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2989 /* Build src & dest name */
2990 lstrcpyW(src, drive);
2991 lstrcatW(src, dir);
2993 /* See if dest is an existing directory */
2994 attribs = GetFileAttributesW(output);
2995 if (attribs != INVALID_FILE_ATTRIBUTES &&
2996 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2997 lstrcpyW(dest, output);
2998 lstrcatW(dest, L"\\");
2999 lstrcatW(dest, fd.cFileName);
3000 } else {
3001 lstrcpyW(dest, output);
3004 lstrcatW(src, fd.cFileName);
3006 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3007 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3009 /* If destination exists, prompt unless /Y supplied */
3010 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
3011 BOOL force = FALSE;
3012 WCHAR copycmd[MAXSTRING];
3013 DWORD len;
3015 /* Default whether automatic overwriting is on. If we are interactive then
3016 we prompt by default, otherwise we overwrite by default
3017 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
3018 if (wcsstr(quals, L"/-Y"))
3019 force = FALSE;
3020 else if (wcsstr(quals, L"/Y"))
3021 force = TRUE;
3022 else {
3023 /* By default, we will force the overwrite in batch mode and ask for
3024 * confirmation in interactive mode. */
3025 force = !interactive;
3026 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
3027 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
3028 * default behavior. */
3029 len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
3030 if (len && len < ARRAY_SIZE(copycmd)) {
3031 if (!lstrcmpiW(copycmd, L"/Y"))
3032 force = TRUE;
3033 else if (!lstrcmpiW(copycmd, L"/-Y"))
3034 force = FALSE;
3038 /* Prompt if overwriting */
3039 if (!force) {
3040 WCHAR* question;
3042 /* Ask for confirmation */
3043 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
3044 ok = WCMD_ask_confirm(question, FALSE, NULL);
3045 LocalFree(question);
3048 if (ok)
3049 flags |= MOVEFILE_REPLACE_EXISTING;
3052 if (ok) {
3053 status = MoveFileExW(src, dest, flags);
3054 } else {
3055 status = TRUE;
3058 if (!status) {
3059 WCMD_print_error ();
3060 errorlevel = 1;
3062 } while (FindNextFileW(hff, &fd) != 0);
3064 FindClose(hff);
3067 /****************************************************************************
3068 * WCMD_pause
3070 * Suspend execution of a batch script until a key is typed
3073 void WCMD_pause (void)
3075 DWORD oldmode;
3076 BOOL have_console;
3077 DWORD count;
3078 WCHAR key;
3079 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
3081 have_console = GetConsoleMode(hIn, &oldmode);
3082 if (have_console)
3083 SetConsoleMode(hIn, 0);
3085 WCMD_output_asis(anykey);
3086 WCMD_ReadFile(hIn, &key, 1, &count);
3087 if (have_console)
3088 SetConsoleMode(hIn, oldmode);
3091 /****************************************************************************
3092 * WCMD_remove_dir
3094 * Delete a directory.
3097 void WCMD_remove_dir (WCHAR *args) {
3099 int argno = 0;
3100 int argsProcessed = 0;
3101 WCHAR *argN = args;
3103 /* Loop through all args */
3104 while (argN) {
3105 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3106 if (argN && argN[0] != '/') {
3107 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
3108 wine_dbgstr_w(quals));
3109 argsProcessed++;
3111 /* If subdirectory search not supplied, just try to remove
3112 and report error if it fails (eg if it contains a file) */
3113 if (wcsstr(quals, L"/S") == NULL) {
3114 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
3116 /* Otherwise use ShFileOp to recursively remove a directory */
3117 } else {
3119 SHFILEOPSTRUCTW lpDir;
3121 /* Ask first */
3122 if (wcsstr(quals, L"/Q") == NULL) {
3123 BOOL ok;
3124 WCHAR question[MAXSTRING];
3126 /* Ask for confirmation */
3127 wsprintfW(question, L"%s ", thisArg);
3128 ok = WCMD_ask_confirm(question, TRUE, NULL);
3130 /* Abort if answer is 'N' */
3131 if (!ok) return;
3134 /* Do the delete */
3135 lpDir.hwnd = NULL;
3136 lpDir.pTo = NULL;
3137 lpDir.pFrom = thisArg;
3138 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
3139 lpDir.wFunc = FO_DELETE;
3141 /* SHFileOperationW needs file list with a double null termination */
3142 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3144 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3149 /* Handle no valid args */
3150 if (argsProcessed == 0) {
3151 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3152 return;
3157 /****************************************************************************
3158 * WCMD_rename
3160 * Rename a file.
3163 void WCMD_rename (void)
3165 BOOL status;
3166 HANDLE hff;
3167 WIN32_FIND_DATAW fd;
3168 WCHAR input[MAX_PATH];
3169 WCHAR *dotDst = NULL;
3170 WCHAR drive[10];
3171 WCHAR dir[MAX_PATH];
3172 WCHAR fname[MAX_PATH];
3173 WCHAR ext[MAX_PATH];
3175 errorlevel = 0;
3177 /* Must be at least two args */
3178 if (param1[0] == 0x00 || param2[0] == 0x00) {
3179 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3180 errorlevel = 1;
3181 return;
3184 /* Destination cannot contain a drive letter or directory separator */
3185 if ((wcschr(param2,':') != NULL) || (wcschr(param2,'\\') != NULL)) {
3186 SetLastError(ERROR_INVALID_PARAMETER);
3187 WCMD_print_error();
3188 errorlevel = 1;
3189 return;
3192 /* Convert partial path to full path */
3193 if (!WCMD_get_fullpath(param1, ARRAY_SIZE(input), input, NULL)) return;
3194 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3195 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3196 dotDst = wcschr(param2, '.');
3198 /* Split into components */
3199 _wsplitpath(input, drive, dir, fname, ext);
3201 hff = FindFirstFileW(input, &fd);
3202 if (hff == INVALID_HANDLE_VALUE)
3203 return;
3205 do {
3206 WCHAR dest[MAX_PATH];
3207 WCHAR src[MAX_PATH];
3208 WCHAR *dotSrc = NULL;
3209 int dirLen;
3211 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3213 /* FIXME: If dest name or extension is *, replace with filename/ext
3214 part otherwise use supplied name. This supports:
3215 ren *.fred *.jim
3216 ren jim.* fred.* etc
3217 However, windows has a more complex algorithm supporting eg
3218 ?'s and *'s mid name */
3219 dotSrc = wcschr(fd.cFileName, '.');
3221 /* Build src & dest name */
3222 lstrcpyW(src, drive);
3223 lstrcatW(src, dir);
3224 lstrcpyW(dest, src);
3225 dirLen = lstrlenW(src);
3226 lstrcatW(src, fd.cFileName);
3228 /* Build name */
3229 if (param2[0] == '*') {
3230 lstrcatW(dest, fd.cFileName);
3231 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3232 } else {
3233 lstrcatW(dest, param2);
3234 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3237 /* Build Extension */
3238 if (dotDst && (*(dotDst+1)=='*')) {
3239 if (dotSrc) lstrcatW(dest, dotSrc);
3240 } else if (dotDst) {
3241 lstrcatW(dest, dotDst);
3244 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3245 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3247 status = MoveFileW(src, dest);
3249 if (!status) {
3250 WCMD_print_error ();
3251 errorlevel = 1;
3253 } while (FindNextFileW(hff, &fd) != 0);
3255 FindClose(hff);
3258 /*****************************************************************************
3259 * WCMD_dupenv
3261 * Make a copy of the environment.
3263 static WCHAR *WCMD_dupenv( const WCHAR *env )
3265 WCHAR *env_copy;
3266 int len;
3268 if( !env )
3269 return NULL;
3271 len = 0;
3272 while ( env[len] )
3273 len += (lstrlenW(&env[len]) + 1);
3275 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3276 if (!env_copy)
3278 WINE_ERR("out of memory\n");
3279 return env_copy;
3281 memcpy (env_copy, env, len*sizeof (WCHAR));
3282 env_copy[len] = 0;
3284 return env_copy;
3287 /*****************************************************************************
3288 * WCMD_setlocal
3290 * setlocal pushes the environment onto a stack
3291 * Save the environment as unicode so we don't screw anything up.
3293 void WCMD_setlocal (const WCHAR *s) {
3294 WCHAR *env;
3295 struct env_stack *env_copy;
3296 WCHAR cwd[MAX_PATH];
3297 BOOL newdelay;
3299 /* setlocal does nothing outside of batch programs */
3300 if (!context) return;
3302 /* DISABLEEXTENSIONS ignored */
3304 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3305 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3306 if (!wcsicmp(param1, L"ENABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"ENABLEDELAYEDEXPANSION")) {
3307 newdelay = TRUE;
3308 } else if (!wcsicmp(param1, L"DISABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"DISABLEDELAYEDEXPANSION")) {
3309 newdelay = FALSE;
3310 } else {
3311 newdelay = delayedsubst;
3313 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3315 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3316 if( !env_copy )
3318 WINE_ERR ("out of memory\n");
3319 return;
3322 env = GetEnvironmentStringsW ();
3323 env_copy->strings = WCMD_dupenv (env);
3324 if (env_copy->strings)
3326 env_copy->batchhandle = context->h;
3327 env_copy->next = saved_environment;
3328 env_copy->delayedsubst = delayedsubst;
3329 delayedsubst = newdelay;
3330 saved_environment = env_copy;
3332 /* Save the current drive letter */
3333 GetCurrentDirectoryW(MAX_PATH, cwd);
3334 env_copy->u.cwd = cwd[0];
3336 else
3337 LocalFree (env_copy);
3339 FreeEnvironmentStringsW (env);
3343 /*****************************************************************************
3344 * WCMD_endlocal
3346 * endlocal pops the environment off a stack
3347 * Note: When searching for '=', search from WCHAR position 1, to handle
3348 * special internal environment variables =C:, =D: etc
3350 void WCMD_endlocal (void) {
3351 WCHAR *env, *old, *p;
3352 struct env_stack *temp;
3353 int len, n;
3355 /* setlocal does nothing outside of batch programs */
3356 if (!context) return;
3358 /* setlocal needs a saved environment from within the same context (batch
3359 program) as it was saved in */
3360 if (!saved_environment || saved_environment->batchhandle != context->h)
3361 return;
3363 /* pop the old environment from the stack */
3364 temp = saved_environment;
3365 saved_environment = temp->next;
3367 /* delete the current environment, totally */
3368 env = GetEnvironmentStringsW ();
3369 old = WCMD_dupenv (env);
3370 len = 0;
3371 while (old[len]) {
3372 n = lstrlenW(&old[len]) + 1;
3373 p = wcschr(&old[len] + 1, '=');
3374 if (p)
3376 *p++ = 0;
3377 SetEnvironmentVariableW (&old[len], NULL);
3379 len += n;
3381 LocalFree (old);
3382 FreeEnvironmentStringsW (env);
3384 /* restore old environment */
3385 env = temp->strings;
3386 len = 0;
3387 delayedsubst = temp->delayedsubst;
3388 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3389 while (env[len]) {
3390 n = lstrlenW(&env[len]) + 1;
3391 p = wcschr(&env[len] + 1, '=');
3392 if (p)
3394 *p++ = 0;
3395 SetEnvironmentVariableW (&env[len], p);
3397 len += n;
3400 /* Restore current drive letter */
3401 if (IsCharAlphaW(temp->u.cwd)) {
3402 WCHAR envvar[4];
3403 WCHAR cwd[MAX_PATH];
3405 wsprintfW(envvar, L"=%c:", temp->u.cwd);
3406 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3407 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3408 SetCurrentDirectoryW(cwd);
3412 LocalFree (env);
3413 LocalFree (temp);
3416 /*****************************************************************************
3417 * WCMD_setshow_default
3419 * Set/Show the current default directory
3422 void WCMD_setshow_default (const WCHAR *args) {
3424 BOOL status;
3425 WCHAR string[1024];
3426 WCHAR cwd[1024];
3427 WCHAR *pos;
3428 WIN32_FIND_DATAW fd;
3429 HANDLE hff;
3431 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3433 /* Skip /D and trailing whitespace if on the front of the command line */
3434 if (lstrlenW(args) >= 2 &&
3435 CompareStringW(LOCALE_USER_DEFAULT,
3436 NORM_IGNORECASE | SORT_STRINGSORT,
3437 args, 2, L"/D", -1) == CSTR_EQUAL) {
3438 args += 2;
3439 while (*args && (*args==' ' || *args=='\t'))
3440 args++;
3443 GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
3445 if (!*args) {
3446 lstrcatW(cwd, L"\r\n");
3447 WCMD_output_asis (cwd);
3449 else {
3450 /* Remove any double quotes, which may be in the
3451 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3452 pos = string;
3453 while (*args) {
3454 if (*args != '"') *pos++ = *args;
3455 args++;
3457 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3458 pos--;
3459 *pos = 0x00;
3461 /* Search for appropriate directory */
3462 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3463 hff = FindFirstFileW(string, &fd);
3464 if (hff != INVALID_HANDLE_VALUE) {
3465 do {
3466 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3467 WCHAR fpath[MAX_PATH];
3468 WCHAR drive[10];
3469 WCHAR dir[MAX_PATH];
3470 WCHAR fname[MAX_PATH];
3471 WCHAR ext[MAX_PATH];
3473 /* Convert path into actual directory spec */
3474 if (!WCMD_get_fullpath(string, ARRAY_SIZE(fpath), fpath, NULL)) return;
3475 _wsplitpath(fpath, drive, dir, fname, ext);
3477 /* Rebuild path */
3478 wsprintfW(string, L"%s%s%s", drive, dir, fd.cFileName);
3479 break;
3481 } while (FindNextFileW(hff, &fd) != 0);
3482 FindClose(hff);
3485 /* Change to that directory */
3486 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3488 status = SetCurrentDirectoryW(string);
3489 if (!status) {
3490 errorlevel = 1;
3491 WCMD_print_error ();
3492 return;
3493 } else {
3495 /* Save away the actual new directory, to store as current location */
3496 GetCurrentDirectoryW(ARRAY_SIZE(string), string);
3498 /* Restore old directory if drive letter would change, and
3499 CD x:\directory /D (or pushd c:\directory) not supplied */
3500 if ((wcsstr(quals, L"/D") == NULL) &&
3501 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3502 SetCurrentDirectoryW(cwd);
3506 /* Set special =C: type environment variable, for drive letter of
3507 change of directory, even if path was restored due to missing
3508 /D (allows changing drive letter when not resident on that
3509 drive */
3510 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3511 WCHAR env[4];
3512 lstrcpyW(env, L"=");
3513 memcpy(env+1, string, 2 * sizeof(WCHAR));
3514 env[3] = 0x00;
3515 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3516 SetEnvironmentVariableW(env, string);
3520 return;
3523 /****************************************************************************
3524 * WCMD_setshow_date
3526 * Set/Show the system date
3527 * FIXME: Can't change date yet
3530 void WCMD_setshow_date (void) {
3532 WCHAR curdate[64], buffer[64];
3533 DWORD count;
3535 if (!*param1) {
3536 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, curdate, ARRAY_SIZE(curdate))) {
3537 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3538 if (wcsstr(quals, L"/T") == NULL) {
3539 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3540 if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count) &&
3541 count > 2) {
3542 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3546 else WCMD_print_error ();
3548 else {
3549 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3553 /****************************************************************************
3554 * WCMD_compare
3555 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3556 * the equals sign.
3558 static int __cdecl WCMD_compare( const void *a, const void *b )
3560 int r;
3561 const WCHAR * const *str_a = a, * const *str_b = b;
3562 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3563 *str_a, wcscspn(*str_a, L"="), *str_b, wcscspn(*str_b, L"=") );
3564 if( r == CSTR_LESS_THAN ) return -1;
3565 if( r == CSTR_GREATER_THAN ) return 1;
3566 return 0;
3569 /****************************************************************************
3570 * WCMD_setshow_sortenv
3572 * sort variables into order for display
3573 * Optionally only display those who start with a stub
3574 * returns the count displayed
3576 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3578 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3579 const WCHAR **str;
3581 if (stub) stublen = lstrlenW(stub);
3583 /* count the number of strings, and the total length */
3584 while ( s[len] ) {
3585 len += (lstrlenW(&s[len]) + 1);
3586 count++;
3589 /* add the strings to an array */
3590 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3591 if( !str )
3592 return 0;
3593 str[0] = s;
3594 for( i=1; i<count; i++ )
3595 str[i] = str[i-1] + lstrlenW(str[i-1]) + 1;
3597 /* sort the array */
3598 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3600 /* print it */
3601 for( i=0; i<count; i++ ) {
3602 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3603 NORM_IGNORECASE | SORT_STRINGSORT,
3604 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3605 /* Don't display special internal variables */
3606 if (str[i][0] != '=') {
3607 WCMD_output_asis(str[i]);
3608 WCMD_output_asis(L"\r\n");
3609 displayedcount++;
3614 LocalFree( str );
3615 return displayedcount;
3618 /****************************************************************************
3619 * WCMD_getprecedence
3620 * Return the precedence of a particular operator
3622 static int WCMD_getprecedence(const WCHAR in)
3624 switch (in) {
3625 case '!':
3626 case '~':
3627 case OP_POSITIVE:
3628 case OP_NEGATIVE:
3629 return 8;
3630 case '*':
3631 case '/':
3632 case '%':
3633 return 7;
3634 case '+':
3635 case '-':
3636 return 6;
3637 case '<':
3638 case '>':
3639 return 5;
3640 case '&':
3641 return 4;
3642 case '^':
3643 return 3;
3644 case '|':
3645 return 2;
3646 case '=':
3647 case OP_ASSSIGNMUL:
3648 case OP_ASSSIGNDIV:
3649 case OP_ASSSIGNMOD:
3650 case OP_ASSSIGNADD:
3651 case OP_ASSSIGNSUB:
3652 case OP_ASSSIGNAND:
3653 case OP_ASSSIGNNOT:
3654 case OP_ASSSIGNOR:
3655 case OP_ASSSIGNSHL:
3656 case OP_ASSSIGNSHR:
3657 return 1;
3658 default:
3659 return 0;
3663 /****************************************************************************
3664 * WCMD_pushnumber
3665 * Push either a number or name (environment variable) onto the supplied
3666 * stack
3668 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3669 VARSTACK *thisstack = heap_xalloc(sizeof(VARSTACK));
3670 thisstack->isnum = (var == NULL);
3671 if (var) {
3672 thisstack->variable = var;
3673 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3674 } else {
3675 thisstack->value = num;
3676 WINE_TRACE("Pushed number %d\n", num);
3678 thisstack->next = *varstack;
3679 *varstack = thisstack;
3682 /****************************************************************************
3683 * WCMD_peeknumber
3684 * Returns the value of the top number or environment variable on the stack
3685 * and leaves the item on the stack.
3687 static int WCMD_peeknumber(VARSTACK **varstack) {
3688 int result = 0;
3689 VARSTACK *thisvar;
3691 if (varstack) {
3692 thisvar = *varstack;
3693 if (!thisvar->isnum) {
3694 WCHAR tmpstr[MAXSTRING];
3695 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3696 result = wcstol(tmpstr,NULL,0);
3698 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3699 } else {
3700 result = thisvar->value;
3703 WINE_TRACE("Peeked number %d\n", result);
3704 return result;
3707 /****************************************************************************
3708 * WCMD_popnumber
3709 * Returns the value of the top number or environment variable on the stack
3710 * and removes the item from the stack.
3712 static int WCMD_popnumber(VARSTACK **varstack) {
3713 int result = 0;
3714 VARSTACK *thisvar;
3716 if (varstack) {
3717 thisvar = *varstack;
3718 result = WCMD_peeknumber(varstack);
3719 if (!thisvar->isnum) heap_free(thisvar->variable);
3720 *varstack = thisvar->next;
3721 heap_free(thisvar);
3723 WINE_TRACE("Popped number %d\n", result);
3724 return result;
3727 /****************************************************************************
3728 * WCMD_pushoperator
3729 * Push an operator onto the supplied stack
3731 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3732 OPSTACK *thisstack = heap_xalloc(sizeof(OPSTACK));
3733 thisstack->precedence = precedence;
3734 thisstack->op = op;
3735 thisstack->next = *opstack;
3736 WINE_TRACE("Pushed operator %c\n", op);
3737 *opstack = thisstack;
3740 /****************************************************************************
3741 * WCMD_popoperator
3742 * Returns the operator from the top of the stack and removes the item from
3743 * the stack.
3745 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3746 WCHAR result = 0;
3747 OPSTACK *thisop;
3749 if (opstack) {
3750 thisop = *opstack;
3751 result = thisop->op;
3752 *opstack = thisop->next;
3753 heap_free(thisop);
3755 WINE_TRACE("Popped operator %c\n", result);
3756 return result;
3759 /****************************************************************************
3760 * WCMD_reduce
3761 * Actions the top operator on the stack against the first and sometimes
3762 * second value on the variable stack, and pushes the result
3763 * Returns non-zero on error.
3765 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3766 WCHAR thisop;
3767 int var1,var2;
3768 int rc = 0;
3770 if (!*opstack || !*varstack) {
3771 WINE_TRACE("No operators for the reduce\n");
3772 return WCMD_NOOPERATOR;
3775 /* Remove the top operator */
3776 thisop = WCMD_popoperator(opstack);
3777 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
3779 /* One variable operators */
3780 var1 = WCMD_popnumber(varstack);
3781 switch (thisop) {
3782 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3783 break;
3784 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3785 break;
3786 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3787 break;
3788 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3789 break;
3792 /* Two variable operators */
3793 if (!*varstack) {
3794 WINE_TRACE("No operands left for the reduce?\n");
3795 return WCMD_NOOPERAND;
3797 switch (thisop) {
3798 case '!':
3799 case '~':
3800 case OP_POSITIVE:
3801 case OP_NEGATIVE:
3802 break; /* Handled above */
3803 case '*': var2 = WCMD_popnumber(varstack);
3804 WCMD_pushnumber(NULL, var2*var1, varstack);
3805 break;
3806 case '/': var2 = WCMD_popnumber(varstack);
3807 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3808 WCMD_pushnumber(NULL, var2/var1, varstack);
3809 break;
3810 case '+': var2 = WCMD_popnumber(varstack);
3811 WCMD_pushnumber(NULL, var2+var1, varstack);
3812 break;
3813 case '-': var2 = WCMD_popnumber(varstack);
3814 WCMD_pushnumber(NULL, var2-var1, varstack);
3815 break;
3816 case '&': var2 = WCMD_popnumber(varstack);
3817 WCMD_pushnumber(NULL, var2&var1, varstack);
3818 break;
3819 case '%': var2 = WCMD_popnumber(varstack);
3820 if (var1 == 0) return WCMD_DIVIDEBYZERO;
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 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3828 which differs from the compiler (for example gcc) so being explicit. */
3829 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3830 WCMD_pushnumber(NULL, 0, varstack);
3831 } else {
3832 WCMD_pushnumber(NULL, var2<<var1, varstack);
3834 break;
3835 case '>': var2 = WCMD_popnumber(varstack);
3836 WCMD_pushnumber(NULL, var2>>var1, varstack);
3837 break;
3838 case '|': var2 = WCMD_popnumber(varstack);
3839 WCMD_pushnumber(NULL, var2|var1, varstack);
3840 break;
3842 case OP_ASSSIGNMUL:
3843 case OP_ASSSIGNDIV:
3844 case OP_ASSSIGNMOD:
3845 case OP_ASSSIGNADD:
3846 case OP_ASSSIGNSUB:
3847 case OP_ASSSIGNAND:
3848 case OP_ASSSIGNNOT:
3849 case OP_ASSSIGNOR:
3850 case OP_ASSSIGNSHL:
3851 case OP_ASSSIGNSHR:
3853 int i = 0;
3855 /* The left of an equals must be one variable */
3856 if (!(*varstack) || (*varstack)->isnum) {
3857 return WCMD_NOOPERAND;
3860 /* Make the number stack grow by inserting the value of the variable */
3861 var2 = WCMD_peeknumber(varstack);
3862 WCMD_pushnumber(NULL, var2, varstack);
3863 WCMD_pushnumber(NULL, var1, varstack);
3865 /* Make the operand stack grow by pushing the assign operator plus the
3866 operator to perform */
3867 while (calcassignments[i].op != ' ' &&
3868 calcassignments[i].calculatedop != thisop) {
3869 i++;
3871 if (calcassignments[i].calculatedop == ' ') {
3872 WINE_ERR("Unexpected operator %c\n", thisop);
3873 return WCMD_NOOPERATOR;
3875 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3876 WCMD_pushoperator(calcassignments[i].op,
3877 WCMD_getprecedence(calcassignments[i].op), opstack);
3878 break;
3881 case '=':
3883 WCHAR result[MAXSTRING];
3885 /* Build the result, then push it onto the stack */
3886 swprintf(result, ARRAY_SIZE(result), L"%d", var1);
3887 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3888 wine_dbgstr_w(result));
3889 SetEnvironmentVariableW((*varstack)->variable, result);
3890 var2 = WCMD_popnumber(varstack);
3891 WCMD_pushnumber(NULL, var1, varstack);
3892 break;
3895 default: WINE_ERR("Unrecognized operator %c\n", thisop);
3898 return rc;
3902 /****************************************************************************
3903 * WCMD_handleExpression
3904 * Handles an expression provided to set /a - If it finds brackets, it uses
3905 * recursion to process the parts in brackets.
3907 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3909 static const WCHAR mathDelims[] = L" \t()!~-*/%+<>&^|=,";
3910 int rc = 0;
3911 WCHAR *pos;
3912 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3913 OPSTACK *opstackhead = NULL;
3914 VARSTACK *varstackhead = NULL;
3915 WCHAR foundhalf = 0;
3917 /* Initialize */
3918 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3919 pos = *expr;
3921 /* Iterate through until whole expression is processed */
3922 while (pos && *pos) {
3923 BOOL treatasnumber;
3925 /* Skip whitespace to get to the next character to process*/
3926 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3927 if (!*pos) goto exprreturn;
3929 /* If we have found anything other than an operator then it's a number/variable */
3930 if (wcschr(mathDelims, *pos) == NULL) {
3931 WCHAR *parmstart, *parm, *dupparm;
3932 WCHAR *nextpos;
3934 /* Cannot have an expression with var/number twice, without an operator
3935 in-between, nor or number following a half constructed << or >> operator */
3936 if (lastwasnumber || foundhalf) {
3937 rc = WCMD_NOOPERATOR;
3938 goto exprerrorreturn;
3940 lastwasnumber = TRUE;
3942 if (iswdigit(*pos)) {
3943 /* For a number - just push it onto the stack */
3944 int num = wcstoul(pos, &nextpos, 0);
3945 WCMD_pushnumber(NULL, num, &varstackhead);
3946 pos = nextpos;
3948 /* Verify the number was validly formed */
3949 if (*nextpos && (wcschr(mathDelims, *nextpos) == NULL)) {
3950 rc = WCMD_BADHEXOCT;
3951 goto exprerrorreturn;
3953 } else {
3955 /* For a variable - just push it onto the stack */
3956 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3957 dupparm = heap_strdupW(parm);
3958 WCMD_pushnumber(dupparm, 0, &varstackhead);
3959 pos = parmstart + lstrlenW(dupparm);
3961 continue;
3964 /* We have found an operator. Some operators are one character, some two, and the minus
3965 and plus signs need special processing as they can be either operators or just influence
3966 the parameter which follows them */
3967 if (foundhalf && (*pos != foundhalf)) {
3968 /* Badly constructed operator pair */
3969 rc = WCMD_NOOPERATOR;
3970 goto exprerrorreturn;
3973 treatasnumber = FALSE; /* We are processing an operand */
3974 switch (*pos) {
3976 /* > and < are special as they are double character operators (and spaces can be between them!)
3977 If we see these for the first time, set a flag, and second time around we continue.
3978 Note these double character operators are stored as just one of the characters on the stack */
3979 case '>':
3980 case '<': if (!foundhalf) {
3981 foundhalf = *pos;
3982 pos++;
3983 break;
3985 /* We have found the rest, so clear up the knowledge of the half completed part and
3986 drop through to normal operator processing */
3987 foundhalf = 0;
3988 /* drop through */
3990 case '=': if (*pos=='=') {
3991 /* = is special cased as if the last was an operator then we may have e.g. += or
3992 *= etc which we need to handle by replacing the operator that is on the stack
3993 with a calculated assignment equivalent */
3994 if (!lastwasnumber && opstackhead) {
3995 int i = 0;
3996 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3997 i++;
3999 if (calcassignments[i].op == ' ') {
4000 rc = WCMD_NOOPERAND;
4001 goto exprerrorreturn;
4002 } else {
4003 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
4004 when the general operator handling happens further down. */
4005 *pos = calcassignments[i].calculatedop;
4006 WCMD_popoperator(&opstackhead);
4010 /* Drop though */
4012 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
4013 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
4014 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
4015 /* drop through */
4016 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
4017 /* drop through */
4019 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
4020 case '!': /* drop through */
4021 case '~': /* drop through */
4022 case '/': /* drop through */
4023 case '%': /* drop through */
4024 case '&': /* drop through */
4025 case '^': /* drop through */
4026 case '*': /* drop through */
4027 case '|':
4028 /* General code for handling most of the operators - look at the
4029 precedence of the top item on the stack, and see if we need to
4030 action the stack before we push something else onto it. */
4032 int precedence = WCMD_getprecedence(*pos);
4033 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
4034 precedence, !opstackhead?-1:opstackhead->precedence);
4036 /* In general, for things with the same precedence, reduce immediately
4037 except for assignments and unary operators which do not */
4038 while (!rc && opstackhead &&
4039 ((opstackhead->precedence > precedence) ||
4040 ((opstackhead->precedence == precedence) &&
4041 (precedence != 1) && (precedence != 8)))) {
4042 rc = WCMD_reduce(&opstackhead, &varstackhead);
4044 if (rc) goto exprerrorreturn;
4045 WCMD_pushoperator(*pos, precedence, &opstackhead);
4046 pos++;
4047 break;
4050 /* comma means start a new expression, ie calculate what we have */
4051 case ',':
4053 int prevresult = -1;
4054 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
4055 while (!rc && opstackhead) {
4056 rc = WCMD_reduce(&opstackhead, &varstackhead);
4058 if (rc) goto exprerrorreturn;
4059 /* If we have anything other than one number left, error
4060 otherwise throw the number away */
4061 if (!varstackhead || varstackhead->next) {
4062 rc = WCMD_NOOPERATOR;
4063 goto exprerrorreturn;
4065 prevresult = WCMD_popnumber(&varstackhead);
4066 WINE_TRACE("Expression resolved to %d\n", prevresult);
4067 heap_free(varstackhead);
4068 varstackhead = NULL;
4069 pos++;
4070 break;
4073 /* Open bracket - use iteration to parse the inner expression, then continue */
4074 case '(' : {
4075 int exprresult = 0;
4076 pos++;
4077 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
4078 if (rc) goto exprerrorreturn;
4079 WCMD_pushnumber(NULL, exprresult, &varstackhead);
4080 break;
4083 /* Close bracket - we have finished this depth, calculate and return */
4084 case ')' : {
4085 pos++;
4086 treatasnumber = TRUE; /* Things in brackets result in a number */
4087 if (depth == 0) {
4088 rc = WCMD_BADPAREN;
4089 goto exprerrorreturn;
4091 goto exprreturn;
4094 default:
4095 WINE_ERR("Unrecognized operator %c\n", *pos);
4096 pos++;
4098 lastwasnumber = treatasnumber;
4101 exprreturn:
4102 *expr = pos;
4104 /* We need to reduce until we have a single number (or variable) on the
4105 stack and set the return value to that */
4106 while (!rc && opstackhead) {
4107 rc = WCMD_reduce(&opstackhead, &varstackhead);
4109 if (rc) goto exprerrorreturn;
4111 /* If we have anything other than one number left, error
4112 otherwise throw the number away */
4113 if (!varstackhead || varstackhead->next) {
4114 rc = WCMD_NOOPERATOR;
4115 goto exprerrorreturn;
4118 /* Now get the number (and convert if it's just a variable name) */
4119 *ret = WCMD_popnumber(&varstackhead);
4121 exprerrorreturn:
4122 /* Free all remaining memory */
4123 while (opstackhead) WCMD_popoperator(&opstackhead);
4124 while (varstackhead) WCMD_popnumber(&varstackhead);
4126 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
4127 return rc;
4130 /****************************************************************************
4131 * WCMD_setshow_env
4133 * Set/Show the environment variables
4136 void WCMD_setshow_env (WCHAR *s) {
4138 LPVOID env;
4139 WCHAR *p;
4140 BOOL status;
4141 WCHAR string[MAXSTRING];
4143 if (param1[0] == 0x00 && quals[0] == 0x00) {
4144 env = GetEnvironmentStringsW();
4145 WCMD_setshow_sortenv( env, NULL );
4146 return;
4149 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4150 if (CompareStringW(LOCALE_USER_DEFAULT,
4151 NORM_IGNORECASE | SORT_STRINGSORT,
4152 s, 2, L"/P", -1) == CSTR_EQUAL) {
4153 DWORD count;
4155 s += 2;
4156 while (*s && (*s==' ' || *s=='\t')) s++;
4157 /* set /P "var=value"jim ignores anything after the last quote */
4158 if (*s=='\"') {
4159 WCHAR *lastquote;
4160 lastquote = WCMD_strip_quotes(s);
4161 if (lastquote) *lastquote = 0x00;
4162 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4165 /* If no parameter, or no '=' sign, return an error */
4166 if (!(*s) || ((p = wcschr (s, '=')) == NULL )) {
4167 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4168 return;
4171 /* Output the prompt */
4172 *p++ = '\0';
4173 if (*p) WCMD_output_asis(p);
4175 /* Read the reply */
4176 if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count) && count > 1) {
4177 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4178 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4179 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4180 wine_dbgstr_w(string));
4181 SetEnvironmentVariableW(s, string);
4184 /* See if /A supplied, and if so calculate the results of all the expressions */
4185 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4186 NORM_IGNORECASE | SORT_STRINGSORT,
4187 s, 2, L"/A", -1) == CSTR_EQUAL) {
4188 /* /A supplied, so evaluate expressions and set variables appropriately */
4189 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4190 /* of the final computation */
4191 int result = 0;
4192 int rc = 0;
4193 WCHAR *thisexpr;
4194 WCHAR *src,*dst;
4196 /* Remove all quotes before doing any calculations */
4197 thisexpr = heap_xalloc((lstrlenW(s+2)+1) * sizeof(WCHAR));
4198 src = s+2;
4199 dst = thisexpr;
4200 while (*src) {
4201 if (*src != '"') *dst++ = *src;
4202 src++;
4204 *dst = 0;
4206 /* Now calculate the results of the expression */
4207 src = thisexpr;
4208 rc = WCMD_handleExpression(&src, &result, 0);
4209 heap_free(thisexpr);
4211 /* If parsing failed, issue the error message */
4212 if (rc > 0) {
4213 WCMD_output_stderr(WCMD_LoadMessage(rc));
4214 return;
4217 /* If we have no context (interactive or cmd.exe /c) print the final result */
4218 if (!context) {
4219 swprintf(string, ARRAY_SIZE(string), L"%d", result);
4220 WCMD_output_asis(string);
4223 } else {
4224 DWORD gle;
4226 /* set "var=value"jim ignores anything after the last quote */
4227 if (*s=='\"') {
4228 WCHAR *lastquote;
4229 lastquote = WCMD_strip_quotes(s);
4230 if (lastquote) *lastquote = 0x00;
4231 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4234 p = wcschr (s, '=');
4235 if (p == NULL) {
4236 env = GetEnvironmentStringsW();
4237 if (WCMD_setshow_sortenv( env, s ) == 0) {
4238 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4239 errorlevel = 1;
4241 return;
4243 *p++ = '\0';
4245 if (!*p) p = NULL;
4246 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4247 wine_dbgstr_w(p));
4248 status = SetEnvironmentVariableW(s, p);
4249 gle = GetLastError();
4250 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4251 errorlevel = 1;
4252 } else if (!status) WCMD_print_error();
4253 else if (!interactive) errorlevel = 0;
4257 /****************************************************************************
4258 * WCMD_setshow_path
4260 * Set/Show the path environment variable
4263 void WCMD_setshow_path (const WCHAR *args) {
4265 WCHAR string[1024];
4266 DWORD status;
4268 if (!*param1 && !*param2) {
4269 status = GetEnvironmentVariableW(L"PATH", string, ARRAY_SIZE(string));
4270 if (status != 0) {
4271 WCMD_output_asis(L"PATH=");
4272 WCMD_output_asis ( string);
4273 WCMD_output_asis(L"\r\n");
4275 else {
4276 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4279 else {
4280 if (*args == '=') args++; /* Skip leading '=' */
4281 status = SetEnvironmentVariableW(L"PATH", args);
4282 if (!status) WCMD_print_error();
4286 /****************************************************************************
4287 * WCMD_setshow_prompt
4289 * Set or show the command prompt.
4292 void WCMD_setshow_prompt (void) {
4294 WCHAR *s;
4296 if (!*param1) {
4297 SetEnvironmentVariableW(L"PROMPT", NULL);
4299 else {
4300 s = param1;
4301 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4302 if (!*s) {
4303 SetEnvironmentVariableW(L"PROMPT", NULL);
4305 else SetEnvironmentVariableW(L"PROMPT", s);
4309 /****************************************************************************
4310 * WCMD_setshow_time
4312 * Set/Show the system time
4313 * FIXME: Can't change time yet
4316 void WCMD_setshow_time (void) {
4318 WCHAR curtime[64], buffer[64];
4319 DWORD count;
4320 SYSTEMTIME st;
4322 if (!*param1) {
4323 GetLocalTime(&st);
4324 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, curtime, ARRAY_SIZE(curtime))) {
4325 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4326 if (wcsstr(quals, L"/T") == NULL) {
4327 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4328 if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count) &&
4329 count > 2) {
4330 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4334 else WCMD_print_error ();
4336 else {
4337 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4341 /****************************************************************************
4342 * WCMD_shift
4344 * Shift batch parameters.
4345 * Optional /n says where to start shifting (n=0-8)
4348 void WCMD_shift (const WCHAR *args) {
4349 int start;
4351 if (context != NULL) {
4352 WCHAR *pos = wcschr(args, '/');
4353 int i;
4355 if (pos == NULL) {
4356 start = 0;
4357 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4358 start = (*(pos+1) - '0');
4359 } else {
4360 SetLastError(ERROR_INVALID_PARAMETER);
4361 WCMD_print_error();
4362 return;
4365 WINE_TRACE("Shifting variables, starting at %d\n", start);
4366 for (i=start;i<=8;i++) {
4367 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4369 context -> shift_count[9] = context -> shift_count[9] + 1;
4374 /****************************************************************************
4375 * WCMD_start
4377 void WCMD_start(WCHAR *args)
4379 int argno;
4380 int have_title;
4381 WCHAR file[MAX_PATH];
4382 WCHAR *cmdline, *cmdline_params;
4383 STARTUPINFOW st;
4384 PROCESS_INFORMATION pi;
4386 GetSystemDirectoryW( file, MAX_PATH );
4387 lstrcatW(file, L"\\start.exe");
4388 cmdline = heap_xalloc( (lstrlenW(file) + lstrlenW(args) + 8) * sizeof(WCHAR) );
4389 lstrcpyW( cmdline, file );
4390 lstrcatW(cmdline, L" ");
4391 cmdline_params = cmdline + lstrlenW(cmdline);
4393 /* The start built-in has some special command-line parsing properties
4394 * which will be outlined here.
4396 * both '\t' and ' ' are argument separators
4397 * '/' has a special double role as both separator and switch prefix, e.g.
4399 * > start /low/i
4400 * or
4401 * > start "title"/i
4403 * are valid ways to pass multiple options to start. In the latter case
4404 * '/i' is not a part of the title but parsed as a switch.
4406 * However, '=', ';' and ',' are not separators:
4407 * > start "deus"=ex,machina
4409 * will in fact open a console titled 'deus=ex,machina'
4411 * The title argument parsing code is only interested in quotes themselves,
4412 * it does not respect escaping of any kind and all quotes are dropped
4413 * from the resulting title, therefore:
4415 * > start "\"" hello"/low
4417 * actually opens a console titled '\ hello' with low priorities.
4419 * To not break compatibility with wine programs relying on
4420 * wine's separate 'start.exe', this program's peculiar console
4421 * title parsing is actually implemented in 'cmd.exe' which is the
4422 * application native Windows programs will use to invoke 'start'.
4424 * WCMD_parameter_with_delims will take care of everything for us.
4426 have_title = FALSE;
4427 for (argno=0; ; argno++) {
4428 WCHAR *thisArg, *argN;
4430 argN = NULL;
4431 thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, L" \t/");
4433 /* No more parameters */
4434 if (!argN)
4435 break;
4437 /* Found the title */
4438 if (argN[0] == '"') {
4439 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
4440 have_title = TRUE;
4442 /* Copy all of the cmdline processed */
4443 memcpy(cmdline_params, args, sizeof(WCHAR) * (argN - args));
4444 cmdline_params[argN - args] = '\0';
4446 /* Add quoted title */
4447 lstrcatW(cmdline_params, L"\"\\\"");
4448 lstrcatW(cmdline_params, thisArg);
4449 lstrcatW(cmdline_params, L"\\\"\"");
4451 /* Concatenate remaining command-line */
4452 thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, L" \t/");
4453 lstrcatW(cmdline_params, argN + lstrlenW(thisArg));
4455 break;
4458 /* Skipping a regular argument? */
4459 else if (argN != args && argN[-1] == '/') {
4460 continue;
4462 /* Not an argument nor the title, start of program arguments,
4463 * stop looking for title.
4465 } else
4466 break;
4469 /* build command-line if not built yet */
4470 if (!have_title) {
4471 lstrcatW( cmdline, args );
4474 memset( &st, 0, sizeof(STARTUPINFOW) );
4475 st.cb = sizeof(STARTUPINFOW);
4477 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4479 WaitForSingleObject( pi.hProcess, INFINITE );
4480 GetExitCodeProcess( pi.hProcess, &errorlevel );
4481 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4482 CloseHandle(pi.hProcess);
4483 CloseHandle(pi.hThread);
4485 else
4487 SetLastError(ERROR_FILE_NOT_FOUND);
4488 WCMD_print_error ();
4489 errorlevel = 9009;
4491 heap_free(cmdline);
4494 /****************************************************************************
4495 * WCMD_title
4497 * Set the console title
4499 void WCMD_title (const WCHAR *args) {
4500 SetConsoleTitleW(args);
4503 /****************************************************************************
4504 * WCMD_type
4506 * Copy a file to standard output.
4509 void WCMD_type (WCHAR *args) {
4511 int argno = 0;
4512 WCHAR *argN = args;
4513 BOOL writeHeaders = FALSE;
4515 if (param1[0] == 0x00) {
4516 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4517 return;
4520 if (param2[0] != 0x00) writeHeaders = TRUE;
4522 /* Loop through all args */
4523 errorlevel = 0;
4524 while (argN) {
4525 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4527 HANDLE h;
4528 WCHAR buffer[512];
4529 DWORD count;
4531 if (!argN) break;
4533 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4534 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4535 FILE_ATTRIBUTE_NORMAL, NULL);
4536 if (h == INVALID_HANDLE_VALUE) {
4537 WCMD_print_error ();
4538 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4539 errorlevel = 1;
4540 } else {
4541 if (writeHeaders) {
4542 WCMD_output_stderr(L"\n%1\n\n\n", thisArg);
4544 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer) - 1, &count)) {
4545 if (count == 0) break; /* ReadFile reports success on EOF! */
4546 buffer[count] = 0;
4547 WCMD_output_asis (buffer);
4549 CloseHandle (h);
4554 /****************************************************************************
4555 * WCMD_more
4557 * Output either a file or stdin to screen in pages
4560 void WCMD_more (WCHAR *args) {
4562 int argno = 0;
4563 WCHAR *argN = args;
4564 WCHAR moreStr[100];
4565 WCHAR moreStrPage[100];
4566 WCHAR buffer[512];
4567 DWORD count;
4569 /* Prefix the NLS more with '-- ', then load the text */
4570 errorlevel = 0;
4571 lstrcpyW(moreStr, L"-- ");
4572 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3], ARRAY_SIZE(moreStr)-3);
4574 if (param1[0] == 0x00) {
4576 /* Wine implements pipes via temporary files, and hence stdin is
4577 effectively reading from the file. This means the prompts for
4578 more are satisfied by the next line from the input (file). To
4579 avoid this, ensure stdin is to the console */
4580 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4581 HANDLE hConIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
4582 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4583 FILE_ATTRIBUTE_NORMAL, 0);
4584 WINE_TRACE("No parms - working probably in pipe mode\n");
4585 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4587 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4588 once you get in this bit unless due to a pipe, it's going to end badly... */
4589 wsprintfW(moreStrPage, L"%s --\n", moreStr);
4591 WCMD_enter_paged_mode(moreStrPage);
4592 while (WCMD_ReadFile(hstdin, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4593 if (count == 0) break; /* ReadFile reports success on EOF! */
4594 buffer[count] = 0;
4595 WCMD_output_asis (buffer);
4597 WCMD_leave_paged_mode();
4599 /* Restore stdin to what it was */
4600 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4601 CloseHandle(hConIn);
4603 return;
4604 } else {
4605 BOOL needsPause = FALSE;
4607 /* Loop through all args */
4608 WINE_TRACE("Parms supplied - working through each file\n");
4609 WCMD_enter_paged_mode(moreStrPage);
4611 while (argN) {
4612 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4613 HANDLE h;
4615 if (!argN) break;
4617 if (needsPause) {
4619 /* Wait */
4620 wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, 100);
4621 WCMD_leave_paged_mode();
4622 WCMD_output_asis(moreStrPage);
4623 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
4624 WCMD_enter_paged_mode(moreStrPage);
4628 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4629 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4630 FILE_ATTRIBUTE_NORMAL, NULL);
4631 if (h == INVALID_HANDLE_VALUE) {
4632 WCMD_print_error ();
4633 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4634 errorlevel = 1;
4635 } else {
4636 ULONG64 curPos = 0;
4637 ULONG64 fileLen = 0;
4638 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4640 /* Get the file size */
4641 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4642 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4644 needsPause = TRUE;
4645 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4646 if (count == 0) break; /* ReadFile reports success on EOF! */
4647 buffer[count] = 0;
4648 curPos += count;
4650 /* Update % count (would be used in WCMD_output_asis as prompt) */
4651 wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, (int) min(99, (curPos * 100)/fileLen));
4653 WCMD_output_asis (buffer);
4655 CloseHandle (h);
4659 WCMD_leave_paged_mode();
4663 /****************************************************************************
4664 * WCMD_verify
4666 * Display verify flag.
4667 * FIXME: We don't actually do anything with the verify flag other than toggle
4668 * it...
4671 void WCMD_verify (const WCHAR *args) {
4673 int count;
4675 count = lstrlenW(args);
4676 if (count == 0) {
4677 if (verify_mode) WCMD_output(WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"ON");
4678 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"OFF");
4679 return;
4681 if (lstrcmpiW(args, L"ON") == 0) {
4682 verify_mode = TRUE;
4683 return;
4685 else if (lstrcmpiW(args, L"OFF") == 0) {
4686 verify_mode = FALSE;
4687 return;
4689 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4692 /****************************************************************************
4693 * WCMD_version
4695 * Display version info.
4698 void WCMD_version (void) {
4700 WCMD_output_asis (version_string);
4704 /****************************************************************************
4705 * WCMD_volume
4707 * Display volume information (set_label = FALSE)
4708 * Additionally set volume label (set_label = TRUE)
4709 * Returns 1 on success, 0 otherwise
4712 int WCMD_volume(BOOL set_label, const WCHAR *path)
4714 DWORD count, serial;
4715 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4716 BOOL status;
4718 if (!*path) {
4719 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
4720 if (!status) {
4721 WCMD_print_error ();
4722 return 0;
4724 status = GetVolumeInformationW(NULL, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4726 else {
4727 if ((path[1] != ':') || (lstrlenW(path) != 2)) {
4728 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4729 return 0;
4731 wsprintfW (curdir, L"%s\\", path);
4732 status = GetVolumeInformationW(curdir, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4734 if (!status) {
4735 WCMD_print_error ();
4736 return 0;
4738 if (label[0] != '\0') {
4739 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4740 curdir[0], label);
4742 else {
4743 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4744 curdir[0]);
4746 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4747 HIWORD(serial), LOWORD(serial));
4748 if (set_label) {
4749 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4750 if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count) &&
4751 count > 1) {
4752 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4753 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4755 if (*path) {
4756 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4758 else {
4759 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4762 return 1;
4765 /**************************************************************************
4766 * WCMD_exit
4768 * Exit either the process, or just this batch program
4772 void WCMD_exit (CMD_LIST **cmdList) {
4773 int rc = wcstol(param1, NULL, 10); /* Note: wcstol of empty parameter is 0 */
4775 if (context && lstrcmpiW(quals, L"/B") == 0) {
4776 errorlevel = rc;
4777 context -> skip_rest = TRUE;
4778 *cmdList = NULL;
4779 } else {
4780 ExitProcess(rc);
4785 /*****************************************************************************
4786 * WCMD_assoc
4788 * Lists or sets file associations (assoc = TRUE)
4789 * Lists or sets file types (assoc = FALSE)
4791 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4793 HKEY key;
4794 DWORD accessOptions = KEY_READ;
4795 WCHAR *newValue;
4796 LONG rc = ERROR_SUCCESS;
4797 WCHAR keyValue[MAXSTRING];
4798 DWORD valueLen;
4799 HKEY readKey;
4801 /* See if parameter includes '=' */
4802 errorlevel = 0;
4803 newValue = wcschr(args, '=');
4804 if (newValue) accessOptions |= KEY_WRITE;
4806 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4807 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, L"", 0, accessOptions, &key) != ERROR_SUCCESS) {
4808 WINE_FIXME("Unexpected failure opening HKCR key: %ld\n", GetLastError());
4809 return;
4812 /* If no parameters then list all associations */
4813 if (*args == 0x00) {
4814 int index = 0;
4816 /* Enumerate all the keys */
4817 while (rc != ERROR_NO_MORE_ITEMS) {
4818 WCHAR keyName[MAXSTRING];
4819 DWORD nameLen;
4821 /* Find the next value */
4822 nameLen = MAXSTRING;
4823 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4825 if (rc == ERROR_SUCCESS) {
4827 /* Only interested in extension ones if assoc, or others
4828 if not assoc */
4829 if ((keyName[0] == '.' && assoc) ||
4830 (!(keyName[0] == '.') && (!assoc)))
4832 WCHAR subkey[MAXSTRING];
4833 lstrcpyW(subkey, keyName);
4834 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4836 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4838 valueLen = sizeof(keyValue);
4839 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4840 WCMD_output_asis(keyName);
4841 WCMD_output_asis(L"=");
4842 /* If no default value found, leave line empty after '=' */
4843 if (rc == ERROR_SUCCESS) {
4844 WCMD_output_asis(keyValue);
4846 WCMD_output_asis(L"\r\n");
4847 RegCloseKey(readKey);
4853 } else {
4855 /* Parameter supplied - if no '=' on command line, it's a query */
4856 if (newValue == NULL) {
4857 WCHAR *space;
4858 WCHAR subkey[MAXSTRING];
4860 /* Query terminates the parameter at the first space */
4861 lstrcpyW(keyValue, args);
4862 space = wcschr(keyValue, ' ');
4863 if (space) *space=0x00;
4865 /* Set up key name */
4866 lstrcpyW(subkey, keyValue);
4867 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4869 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4871 valueLen = sizeof(keyValue);
4872 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4873 WCMD_output_asis(args);
4874 WCMD_output_asis(L"=");
4875 /* If no default value found, leave line empty after '=' */
4876 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4877 WCMD_output_asis(L"\r\n");
4878 RegCloseKey(readKey);
4880 } else {
4881 WCHAR msgbuffer[MAXSTRING];
4883 /* Load the translated 'File association not found' */
4884 if (assoc) {
4885 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4886 } else {
4887 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4889 WCMD_output_stderr(msgbuffer, keyValue);
4890 errorlevel = 2;
4893 /* Not a query - it's a set or clear of a value */
4894 } else {
4896 WCHAR subkey[MAXSTRING];
4898 /* Get pointer to new value */
4899 *newValue = 0x00;
4900 newValue++;
4902 /* Set up key name */
4903 lstrcpyW(subkey, args);
4904 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4906 /* If nothing after '=' then clear value - only valid for ASSOC */
4907 if (*newValue == 0x00) {
4909 if (assoc) rc = RegDeleteKeyW(key, args);
4910 if (assoc && rc == ERROR_SUCCESS) {
4911 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4913 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4914 WCMD_print_error();
4915 errorlevel = 2;
4917 } else {
4918 WCHAR msgbuffer[MAXSTRING];
4920 /* Load the translated 'File association not found' */
4921 if (assoc) {
4922 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4923 } else {
4924 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4926 WCMD_output_stderr(msgbuffer, args);
4927 errorlevel = 2;
4930 /* It really is a set value = contents */
4931 } else {
4932 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4933 accessOptions, NULL, &readKey, NULL);
4934 if (rc == ERROR_SUCCESS) {
4935 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4936 (LPBYTE)newValue,
4937 sizeof(WCHAR) * (lstrlenW(newValue) + 1));
4938 RegCloseKey(readKey);
4941 if (rc != ERROR_SUCCESS) {
4942 WCMD_print_error();
4943 errorlevel = 2;
4944 } else {
4945 WCMD_output_asis(args);
4946 WCMD_output_asis(L"=");
4947 WCMD_output_asis(newValue);
4948 WCMD_output_asis(L"\r\n");
4954 /* Clean up */
4955 RegCloseKey(key);
4958 /****************************************************************************
4959 * WCMD_color
4961 * Colors the terminal screen.
4964 void WCMD_color (void) {
4966 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4967 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4969 if (param1[0] != 0x00 && lstrlenW(param1) > 2) {
4970 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4971 return;
4974 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4976 COORD topLeft;
4977 DWORD screenSize;
4978 DWORD color = 0;
4980 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4982 topLeft.X = 0;
4983 topLeft.Y = 0;
4985 /* Convert the color hex digits */
4986 if (param1[0] == 0x00) {
4987 color = defaultColor;
4988 } else {
4989 color = wcstoul(param1, NULL, 16);
4992 /* Fail if fg == bg color */
4993 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4994 errorlevel = 1;
4995 return;
4998 /* Set the current screen contents and ensure all future writes
4999 remain this color */
5000 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
5001 SetConsoleTextAttribute(hStdOut, color);
5005 /****************************************************************************
5006 * WCMD_mklink
5009 void WCMD_mklink(WCHAR *args)
5011 int argno = 0;
5012 WCHAR *argN = args;
5013 BOOL isdir = FALSE;
5014 BOOL junction = FALSE;
5015 BOOL hard = FALSE;
5016 BOOL ret = FALSE;
5017 WCHAR file1[MAX_PATH];
5018 WCHAR file2[MAX_PATH];
5020 if (param1[0] == 0x00 || param2[0] == 0x00) {
5021 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
5022 return;
5025 file1[0] = 0;
5027 while (argN) {
5028 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
5030 if (!argN) break;
5032 WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
5034 if (lstrcmpiW(thisArg, L"/D") == 0)
5035 isdir = TRUE;
5036 else if (lstrcmpiW(thisArg, L"/H") == 0)
5037 hard = TRUE;
5038 else if (lstrcmpiW(thisArg, L"/J") == 0)
5039 junction = TRUE;
5040 else {
5041 if(!file1[0])
5042 lstrcpyW(file1, thisArg);
5043 else
5044 lstrcpyW(file2, thisArg);
5048 if(hard)
5049 ret = CreateHardLinkW(file1, file2, NULL);
5050 else if(!junction)
5051 ret = CreateSymbolicLinkW(file1, file2, isdir);
5052 else
5053 WINE_TRACE("Juction links currently not supported.\n");
5055 if(!ret)
5056 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), file1);