ntoskrnl.exe/tests: Add some IOCTL_HID_WRITE_REPORT tests.
[wine.git] / programs / cmd / builtins.c
blobc7df724ae005f365909f9e6f21fe80f598548b4f
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 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, ARRAY_SIZE(answer), &count);
197 answer[0] = towupper(answer[0]);
198 if (answer[0] == Ybuffer[0])
199 return TRUE;
200 if (answer[0] == Nbuffer[0])
201 return FALSE;
202 if (optionAll && answer[0] == Abuffer[0])
204 *optionAll = TRUE;
205 return TRUE;
210 /****************************************************************************
211 * WCMD_clear_screen
213 * Clear the terminal screen.
216 void WCMD_clear_screen (void) {
218 /* Emulate by filling the screen from the top left to bottom right with
219 spaces, then moving the cursor to the top left afterwards */
220 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
221 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
223 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
225 COORD topLeft;
226 DWORD screenSize, written;
228 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
230 topLeft.X = 0;
231 topLeft.Y = 0;
232 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
233 FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
234 SetConsoleCursorPosition(hStdOut, topLeft);
238 /****************************************************************************
239 * WCMD_change_tty
241 * Change the default i/o device (ie redirect STDin/STDout).
244 void WCMD_change_tty (void) {
246 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
250 /****************************************************************************
251 * WCMD_choice
255 void WCMD_choice (const WCHAR * args) {
256 WCHAR answer[16];
257 WCHAR buffer[16];
258 WCHAR *ptr = NULL;
259 WCHAR *opt_c = NULL;
260 WCHAR *my_command = NULL;
261 WCHAR opt_default = 0;
262 DWORD opt_timeout = 0;
263 DWORD count;
264 DWORD oldmode;
265 BOOL have_console;
266 BOOL opt_n = FALSE;
267 BOOL opt_s = FALSE;
269 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
270 errorlevel = 0;
272 my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
274 ptr = WCMD_skip_leading_spaces(my_command);
275 while (*ptr == '/') {
276 switch (towupper(ptr[1])) {
277 case 'C':
278 ptr += 2;
279 /* the colon is optional */
280 if (*ptr == ':')
281 ptr++;
283 if (!*ptr || iswspace(*ptr)) {
284 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
285 heap_free(my_command);
286 return;
289 /* remember the allowed keys (overwrite previous /C option) */
290 opt_c = ptr;
291 while (*ptr && (!iswspace(*ptr)))
292 ptr++;
294 if (*ptr) {
295 /* terminate allowed chars */
296 *ptr = 0;
297 ptr = WCMD_skip_leading_spaces(&ptr[1]);
299 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
300 break;
302 case 'N':
303 opt_n = TRUE;
304 ptr = WCMD_skip_leading_spaces(&ptr[2]);
305 break;
307 case 'S':
308 opt_s = TRUE;
309 ptr = WCMD_skip_leading_spaces(&ptr[2]);
310 break;
312 case 'T':
313 ptr = &ptr[2];
314 /* the colon is optional */
315 if (*ptr == ':')
316 ptr++;
318 opt_default = *ptr++;
320 if (!opt_default || (*ptr != ',')) {
321 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
322 heap_free(my_command);
323 return;
325 ptr++;
327 count = 0;
328 while (((answer[count] = *ptr)) && iswdigit(*ptr) && (count < 15)) {
329 count++;
330 ptr++;
333 answer[count] = 0;
334 opt_timeout = wcstol(answer, NULL, 10);
336 ptr = WCMD_skip_leading_spaces(ptr);
337 break;
339 default:
340 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
341 heap_free(my_command);
342 return;
346 if (opt_timeout)
347 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
349 if (have_console)
350 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
352 /* use default keys, when needed: localized versions of "Y"es and "No" */
353 if (!opt_c) {
354 LoadStringW(hinst, WCMD_YES, buffer, ARRAY_SIZE(buffer));
355 LoadStringW(hinst, WCMD_NO, buffer + 1, ARRAY_SIZE(buffer) - 1);
356 opt_c = buffer;
357 buffer[2] = 0;
360 /* print the question, when needed */
361 if (*ptr)
362 WCMD_output_asis(ptr);
364 if (!opt_s) {
365 wcsupr(opt_c);
366 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
369 if (!opt_n) {
370 /* print a list of all allowed answers inside brackets */
371 WCMD_output_asis(L"[");
372 ptr = opt_c;
373 answer[1] = 0;
374 while ((answer[0] = *ptr++)) {
375 WCMD_output_asis(answer);
376 if (*ptr)
377 WCMD_output_asis(L",");
379 WCMD_output_asis(L"]?");
382 while (TRUE) {
384 /* FIXME: Add support for option /T */
385 answer[1] = 0; /* terminate single character string */
386 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
388 if (!opt_s)
389 answer[0] = towupper(answer[0]);
391 ptr = wcschr(opt_c, answer[0]);
392 if (ptr) {
393 WCMD_output_asis(answer);
394 WCMD_output_asis(L"\r\n");
395 if (have_console)
396 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
398 errorlevel = (ptr - opt_c) + 1;
399 WINE_TRACE("answer: %d\n", errorlevel);
400 heap_free(my_command);
401 return;
403 else
405 /* key not allowed: play the bell */
406 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
407 WCMD_output_asis(L"\a");
412 /****************************************************************************
413 * WCMD_AppendEOF
415 * Adds an EOF onto the end of a file
416 * Returns TRUE on success
418 static BOOL WCMD_AppendEOF(WCHAR *filename)
420 HANDLE h;
421 DWORD bytes_written;
423 char eof = '\x1a';
425 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
426 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
427 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
429 if (h == INVALID_HANDLE_VALUE) {
430 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
431 return FALSE;
432 } else {
433 SetFilePointer (h, 0, NULL, FILE_END);
434 if (!WriteFile(h, &eof, 1, &bytes_written, NULL)) {
435 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
436 CloseHandle(h);
437 return FALSE;
439 CloseHandle(h);
441 return TRUE;
444 /****************************************************************************
445 * WCMD_IsSameFile
447 * Checks if the two paths reference to the same file
449 static BOOL WCMD_IsSameFile(const WCHAR *name1, const WCHAR *name2)
451 BOOL ret = FALSE;
452 HANDLE file1 = INVALID_HANDLE_VALUE, file2 = INVALID_HANDLE_VALUE;
453 BY_HANDLE_FILE_INFORMATION info1, info2;
455 file1 = CreateFileW(name1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
456 if (file1 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file1, &info1))
457 goto end;
459 file2 = CreateFileW(name2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
460 if (file2 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file2, &info2))
461 goto end;
463 ret = info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
464 && info1.nFileIndexHigh == info2.nFileIndexHigh
465 && info1.nFileIndexLow == info2.nFileIndexLow;
466 end:
467 if (file1 != INVALID_HANDLE_VALUE)
468 CloseHandle(file1);
469 if (file2 != INVALID_HANDLE_VALUE)
470 CloseHandle(file2);
471 return ret;
474 /****************************************************************************
475 * WCMD_ManualCopy
477 * Copies from a file
478 * optionally reading only until EOF (ascii copy)
479 * optionally appending onto an existing file (append)
480 * Returns TRUE on success
482 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
484 HANDLE in,out;
485 BOOL ok;
486 DWORD bytesread, byteswritten;
488 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
489 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
491 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
492 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
493 if (in == INVALID_HANDLE_VALUE) {
494 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
495 return FALSE;
498 /* Open the output file, overwriting if not appending */
499 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
500 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
501 if (out == INVALID_HANDLE_VALUE) {
502 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
503 CloseHandle(in);
504 return FALSE;
507 /* Move to end of destination if we are going to append to it */
508 if (append) {
509 SetFilePointer(out, 0, NULL, FILE_END);
512 /* Loop copying data from source to destination until EOF read */
515 char buffer[MAXSTRING];
517 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
518 if (ok) {
520 /* Stop at first EOF */
521 if (ascii) {
522 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
523 if (ptr) bytesread = (ptr - buffer);
526 if (bytesread) {
527 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
528 if (!ok || byteswritten != bytesread) {
529 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
530 wine_dbgstr_w(dstname), GetLastError());
533 } else {
534 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
535 wine_dbgstr_w(srcname), GetLastError());
537 } while (ok && bytesread > 0);
539 CloseHandle(out);
540 CloseHandle(in);
541 return ok;
544 /****************************************************************************
545 * WCMD_copy
547 * Copy a file or wildcarded set.
548 * For ascii/binary type copies, it gets complex:
549 * Syntax on command line is
550 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
551 * Where first /a or /b sets 'mode in operation' until another is found
552 * once another is found, it applies to the file preceding the /a or /b
553 * In addition each filename can contain wildcards
554 * To make matters worse, the + may be in the same parameter (i.e. no
555 * whitespace) or with whitespace separating it
557 * ASCII mode on read == read and stop at first EOF
558 * ASCII mode on write == append EOF to destination
559 * Binary == copy as-is
561 * Design of this is to build up a list of files which will be copied into a
562 * list, then work through the list file by file.
563 * If no destination is specified, it defaults to the name of the first file in
564 * the list, but the current directory.
568 void WCMD_copy(WCHAR * args) {
570 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
571 WCHAR *thisparam;
572 int argno = 0;
573 WCHAR *rawarg;
574 WIN32_FIND_DATAW fd;
575 HANDLE hff = INVALID_HANDLE_VALUE;
576 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
577 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
578 BOOL anyconcats = FALSE; /* Have we found any + options */
579 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
580 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
581 BOOL prompt; /* Prompt before overwriting */
582 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
583 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
584 BOOL status;
585 WCHAR copycmd[4];
586 DWORD len;
587 BOOL dstisdevice = FALSE;
589 typedef struct _COPY_FILES
591 struct _COPY_FILES *next;
592 BOOL concatenate;
593 WCHAR *name;
594 int binarycopy;
595 } COPY_FILES;
596 COPY_FILES *sourcelist = NULL;
597 COPY_FILES *lastcopyentry = NULL;
598 COPY_FILES *destination = NULL;
599 COPY_FILES *thiscopy = NULL;
600 COPY_FILES *prevcopy = NULL;
602 /* Assume we were successful! */
603 errorlevel = 0;
605 /* If no args supplied at all, report an error */
606 if (param1[0] == 0x00) {
607 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
608 errorlevel = 1;
609 return;
612 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
614 /* Walk through all args, building up a list of files to process */
615 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
616 while (*(thisparam)) {
617 WCHAR *pos1, *pos2;
618 BOOL inquotes;
620 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
622 /* Handle switches */
623 if (*thisparam == '/') {
624 while (*thisparam == '/') {
625 thisparam++;
626 if (towupper(*thisparam) == 'D') {
627 opt_d = TRUE;
628 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
629 } else if (towupper(*thisparam) == 'Y') {
630 opt_y = TRUE;
631 } else if (towupper(*thisparam) == '-' && towupper(*(thisparam+1)) == 'Y') {
632 opt_noty = TRUE;
633 } else if (towupper(*thisparam) == 'V') {
634 opt_v = TRUE;
635 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
636 } else if (towupper(*thisparam) == 'N') {
637 opt_n = TRUE;
638 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
639 } else if (towupper(*thisparam) == 'Z') {
640 opt_z = TRUE;
641 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
642 } else if (towupper(*thisparam) == 'A') {
643 if (binarymode != 0) {
644 binarymode = 0;
645 WINE_TRACE("Subsequent files will be handled as ASCII\n");
646 if (destination != NULL) {
647 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
648 destination->binarycopy = binarymode;
649 } else if (lastcopyentry != NULL) {
650 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
651 lastcopyentry->binarycopy = binarymode;
654 } else if (towupper(*thisparam) == 'B') {
655 if (binarymode != 1) {
656 binarymode = 1;
657 WINE_TRACE("Subsequent files will be handled as binary\n");
658 if (destination != NULL) {
659 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
660 destination->binarycopy = binarymode;
661 } else if (lastcopyentry != NULL) {
662 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
663 lastcopyentry->binarycopy = binarymode;
666 } else {
667 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
669 thisparam++;
672 /* This parameter was purely switches, get the next one */
673 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
674 continue;
677 /* We have found something which is not a switch. If could be anything of the form
678 sourcefilename (which could be destination too)
679 + (when filename + filename syntex used)
680 sourcefilename+sourcefilename
681 +sourcefilename
682 +/b[tests show windows then ignores to end of parameter]
685 if (*thisparam=='+') {
686 if (lastcopyentry == NULL) {
687 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
688 errorlevel = 1;
689 goto exitreturn;
690 } else {
691 concatnextfilename = TRUE;
692 anyconcats = TRUE;
695 /* Move to next thing to process */
696 thisparam++;
697 if (*thisparam == 0x00)
698 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
699 continue;
702 /* We have found something to process - build a COPY_FILE block to store it */
703 thiscopy = heap_xalloc(sizeof(COPY_FILES));
705 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
706 thiscopy->concatenate = concatnextfilename;
707 thiscopy->binarycopy = binarymode;
708 thiscopy->next = NULL;
710 /* Time to work out the name. Allocate at least enough space (deliberately too much to
711 leave space to append \* to the end) , then copy in character by character. Strip off
712 quotes if we find them. */
713 len = lstrlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
714 thiscopy->name = heap_xalloc(len*sizeof(WCHAR));
715 memset(thiscopy->name, 0x00, len);
717 pos1 = thisparam;
718 pos2 = thiscopy->name;
719 inquotes = FALSE;
720 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
721 if (*pos1 == '"') {
722 inquotes = !inquotes;
723 pos1++;
724 } else *pos2++ = *pos1++;
726 *pos2 = 0;
727 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
729 /* This is either the first source, concatenated subsequent source or destination */
730 if (sourcelist == NULL) {
731 WINE_TRACE("Adding as first source part\n");
732 sourcelist = thiscopy;
733 lastcopyentry = thiscopy;
734 } else if (concatnextfilename) {
735 WINE_TRACE("Adding to source file list to be concatenated\n");
736 lastcopyentry->next = thiscopy;
737 lastcopyentry = thiscopy;
738 } else if (destination == NULL) {
739 destination = thiscopy;
740 } else {
741 /* We have processed sources and destinations and still found more to do - invalid */
742 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
743 errorlevel = 1;
744 goto exitreturn;
746 concatnextfilename = FALSE;
748 /* We either need to process the rest of the parameter or move to the next */
749 if (*pos1 == '/' || *pos1 == '+') {
750 thisparam = pos1;
751 continue;
752 } else {
753 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
757 /* Ensure we have at least one source file */
758 if (!sourcelist) {
759 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
760 errorlevel = 1;
761 goto exitreturn;
764 /* Default whether automatic overwriting is on. If we are interactive then
765 we prompt by default, otherwise we overwrite by default
766 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
767 if (opt_noty) prompt = TRUE;
768 else if (opt_y) prompt = FALSE;
769 else {
770 /* By default, we will force the overwrite in batch mode and ask for
771 * confirmation in interactive mode. */
772 prompt = interactive;
773 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
774 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
775 * default behavior. */
776 len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
777 if (len && len < ARRAY_SIZE(copycmd)) {
778 if (!lstrcmpiW(copycmd, L"/Y"))
779 prompt = FALSE;
780 else if (!lstrcmpiW(copycmd, L"/-Y"))
781 prompt = TRUE;
785 /* Calculate the destination now - if none supplied, it's current dir +
786 filename of first file in list*/
787 if (destination == NULL) {
789 WINE_TRACE("No destination supplied, so need to calculate it\n");
790 lstrcpyW(destname, L".");
791 lstrcatW(destname, L"\\");
793 destination = heap_xalloc(sizeof(COPY_FILES));
794 if (destination == NULL) goto exitreturn;
795 destination->concatenate = FALSE; /* Not used for destination */
796 destination->binarycopy = binarymode;
797 destination->next = NULL; /* Not used for destination */
798 destination->name = NULL; /* To be filled in */
799 destisdirectory = TRUE;
801 } else {
802 WCHAR *filenamepart;
803 DWORD attributes;
805 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
807 /* Convert to fully qualified path/filename */
808 GetFullPathNameW(destination->name, ARRAY_SIZE(destname), destname, &filenamepart);
809 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
811 /* If parameter is a directory, ensure it ends in \ */
812 attributes = GetFileAttributesW(destname);
813 if (ends_with_backslash( destname ) ||
814 ((attributes != INVALID_FILE_ATTRIBUTES) &&
815 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
817 destisdirectory = TRUE;
818 if (!ends_with_backslash(destname)) lstrcatW(destname, L"\\");
819 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
823 /* Normally, the destination is the current directory unless we are
824 concatenating, in which case it's current directory plus first filename.
825 Note that if the
826 In addition by default it is a binary copy unless concatenating, when
827 the copy defaults to an ascii copy (stop at EOF). We do not know the
828 first source part yet (until we search) so flag as needing filling in. */
830 if (anyconcats) {
831 /* We have found an a+b type syntax, so destination has to be a filename
832 and we need to default to ascii copying. If we have been supplied a
833 directory as the destination, we need to defer calculating the name */
834 if (destisdirectory) appendfirstsource = TRUE;
835 if (destination->binarycopy == -1) destination->binarycopy = 0;
837 } else if (!destisdirectory) {
838 /* We have been asked to copy to a filename. Default to ascii IF the
839 source contains wildcards (true even if only one match) */
840 if (wcspbrk(sourcelist->name, L"*?") != NULL) {
841 anyconcats = TRUE; /* We really are concatenating to a single file */
842 if (destination->binarycopy == -1) {
843 destination->binarycopy = 0;
845 } else {
846 if (destination->binarycopy == -1) {
847 destination->binarycopy = 1;
852 /* Save away the destination name*/
853 heap_free(destination->name);
854 destination->name = heap_strdupW(destname);
855 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
856 wine_dbgstr_w(destname), appendfirstsource);
858 /* Remember if the destination is a device */
859 if (wcsncmp(destination->name, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
860 WINE_TRACE("Destination is a device\n");
861 dstisdevice = TRUE;
864 /* Now we need to walk the set of sources, and process each name we come to.
865 If anyconcats is true, we are writing to one file, otherwise we are using
866 the source name each time.
867 If destination exists, prompt for overwrite the first time (if concatenating
868 we ask each time until yes is answered)
869 The first source file we come across must exist (when wildcards expanded)
870 and if concatenating with overwrite prompts, each source file must exist
871 until a yes is answered. */
873 thiscopy = sourcelist;
874 prevcopy = NULL;
876 while (thiscopy != NULL) {
878 WCHAR srcpath[MAX_PATH];
879 const WCHAR *srcname;
880 WCHAR *filenamepart;
881 DWORD attributes;
882 BOOL srcisdevice = FALSE;
884 /* If it was not explicit, we now know whether we are concatenating or not and
885 hence whether to copy as binary or ascii */
886 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
888 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
889 to where the filename portion begins (used for wildcard expansion). */
890 GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
891 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
893 /* If parameter is a directory, ensure it ends in \* */
894 attributes = GetFileAttributesW(srcpath);
895 if (ends_with_backslash( srcpath )) {
897 /* We need to know where the filename part starts, so append * and
898 recalculate the full resulting path */
899 lstrcatW(thiscopy->name, L"*");
900 GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
901 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
903 } else if ((wcspbrk(srcpath, L"*?") == NULL) &&
904 (attributes != INVALID_FILE_ATTRIBUTES) &&
905 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
907 /* We need to know where the filename part starts, so append \* and
908 recalculate the full resulting path */
909 lstrcatW(thiscopy->name, L"\\*");
910 GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
911 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
914 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
915 wine_dbgstr_w(srcpath), anyconcats);
917 /* If the source is a device, just use it, otherwise search */
918 if (wcsncmp(srcpath, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
919 WINE_TRACE("Source is a device\n");
920 srcisdevice = TRUE;
921 srcname = &srcpath[4]; /* After the \\.\ prefix */
922 } else {
924 /* Loop through all source files */
925 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
926 hff = FindFirstFileW(srcpath, &fd);
927 if (hff != INVALID_HANDLE_VALUE) {
928 srcname = fd.cFileName;
932 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
933 do {
934 WCHAR outname[MAX_PATH];
935 BOOL overwrite;
936 BOOL appendtofirstfile = FALSE;
938 /* Skip . and .., and directories */
939 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
940 WINE_TRACE("Skipping directories\n");
941 } else {
943 /* Build final destination name */
944 lstrcpyW(outname, destination->name);
945 if (destisdirectory || appendfirstsource) lstrcatW(outname, srcname);
947 /* Build source name */
948 if (!srcisdevice) lstrcpyW(filenamepart, srcname);
950 /* Do we just overwrite (we do if we are writing to a device) */
951 overwrite = !prompt;
952 if (dstisdevice || (anyconcats && writtenoneconcat)) {
953 overwrite = TRUE;
956 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
957 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
958 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
959 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
961 if (!writtenoneconcat) {
962 appendtofirstfile = anyconcats && WCMD_IsSameFile(srcpath, outname);
965 /* Prompt before overwriting */
966 if (appendtofirstfile) {
967 overwrite = TRUE;
968 } else if (!overwrite) {
969 DWORD attributes = GetFileAttributesW(outname);
970 if (attributes != INVALID_FILE_ATTRIBUTES) {
971 WCHAR* question;
972 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
973 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
974 LocalFree(question);
976 else overwrite = TRUE;
979 /* If we needed to save away the first filename, do it */
980 if (appendfirstsource && overwrite) {
981 heap_free(destination->name);
982 destination->name = heap_strdupW(outname);
983 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
984 appendfirstsource = FALSE;
985 destisdirectory = FALSE;
988 /* Do the copy as appropriate */
989 if (overwrite) {
990 if (anyconcats && WCMD_IsSameFile(srcpath, outname)) {
991 /* Silently skip if the destination file is also a source file */
992 status = TRUE;
993 } else if (anyconcats && writtenoneconcat) {
994 if (thiscopy->binarycopy) {
995 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
996 } else {
997 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
999 } else if (!thiscopy->binarycopy) {
1000 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
1001 } else if (srcisdevice) {
1002 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
1003 } else {
1004 status = CopyFileW(srcpath, outname, FALSE);
1006 if (!status) {
1007 WCMD_print_error ();
1008 errorlevel = 1;
1009 } else {
1010 WINE_TRACE("Copied successfully\n");
1011 if (anyconcats) writtenoneconcat = TRUE;
1013 /* Append EOF if ascii destination and we are not going to add more onto the end
1014 Note: Testing shows windows has an optimization whereas if you have a binary
1015 copy of a file to a single destination (ie concatenation) then it does not add
1016 the EOF, hence the check on the source copy type below. */
1017 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
1018 if (!WCMD_AppendEOF(outname)) {
1019 WCMD_print_error ();
1020 errorlevel = 1;
1026 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1027 if (!srcisdevice) FindClose (hff);
1028 } else {
1029 /* Error if the first file was not found */
1030 if (!anyconcats || !writtenoneconcat) {
1031 WCMD_print_error ();
1032 errorlevel = 1;
1036 /* Step on to the next supplied source */
1037 thiscopy = thiscopy -> next;
1040 /* Append EOF if ascii destination and we were concatenating */
1041 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
1042 if (!WCMD_AppendEOF(destination->name)) {
1043 WCMD_print_error ();
1044 errorlevel = 1;
1048 /* Exit out of the routine, freeing any remaining allocated memory */
1049 exitreturn:
1051 thiscopy = sourcelist;
1052 while (thiscopy != NULL) {
1053 prevcopy = thiscopy;
1054 /* Free up this block*/
1055 thiscopy = thiscopy -> next;
1056 heap_free(prevcopy->name);
1057 heap_free(prevcopy);
1060 /* Free up the destination memory */
1061 if (destination) {
1062 heap_free(destination->name);
1063 heap_free(destination);
1066 return;
1069 /****************************************************************************
1070 * WCMD_create_dir
1072 * Create a directory (and, if needed, any intermediate directories).
1074 * Modifies its argument by replacing slashes temporarily with nulls.
1077 static BOOL create_full_path(WCHAR* path)
1079 WCHAR *p, *start;
1081 /* don't mess with drive letter portion of path, if any */
1082 start = path;
1083 if (path[1] == ':')
1084 start = path+2;
1086 /* Strip trailing slashes. */
1087 for (p = path + lstrlenW(path) - 1; p != start && *p == '\\'; p--)
1088 *p = 0;
1090 /* Step through path, creating intermediate directories as needed. */
1091 /* First component includes drive letter, if any. */
1092 p = start;
1093 for (;;) {
1094 DWORD rv;
1095 /* Skip to end of component */
1096 while (*p == '\\') p++;
1097 while (*p && *p != '\\') p++;
1098 if (!*p) {
1099 /* path is now the original full path */
1100 return CreateDirectoryW(path, NULL);
1102 /* Truncate path, create intermediate directory, and restore path */
1103 *p = 0;
1104 rv = CreateDirectoryW(path, NULL);
1105 *p = '\\';
1106 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1107 return FALSE;
1109 /* notreached */
1110 return FALSE;
1113 void WCMD_create_dir (WCHAR *args) {
1114 int argno = 0;
1115 WCHAR *argN = args;
1117 if (param1[0] == 0x00) {
1118 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1119 return;
1121 /* Loop through all args */
1122 while (TRUE) {
1123 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1124 if (!argN) break;
1125 if (!create_full_path(thisArg)) {
1126 WCMD_print_error ();
1127 errorlevel = 1;
1132 /* Parse the /A options given by the user on the commandline
1133 * into a bitmask of wanted attributes (*wantSet),
1134 * and a bitmask of unwanted attributes (*wantClear).
1136 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1137 WCHAR *p;
1139 /* both are strictly 'out' parameters */
1140 *wantSet=0;
1141 *wantClear=0;
1143 /* For each /A argument */
1144 for (p=wcsstr(quals, L"/A"); p != NULL; p=wcsstr(p, L"/A")) {
1145 /* Skip /A itself */
1146 p += 2;
1148 /* Skip optional : */
1149 if (*p == ':') p++;
1151 /* For each of the attribute specifier chars to this /A option */
1152 for (; *p != 0 && *p != '/'; p++) {
1153 BOOL negate = FALSE;
1154 DWORD mask = 0;
1156 if (*p == '-') {
1157 negate=TRUE;
1158 p++;
1161 /* Convert the attribute specifier to a bit in one of the masks */
1162 switch (*p) {
1163 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1164 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1165 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1166 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1167 default:
1168 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1170 if (negate)
1171 *wantClear |= mask;
1172 else
1173 *wantSet |= mask;
1178 /* If filename part of parameter is * or *.*,
1179 * and neither /Q nor /P options were given,
1180 * prompt the user whether to proceed.
1181 * Returns FALSE if user says no, TRUE otherwise.
1182 * *pPrompted is set to TRUE if the user is prompted.
1183 * (If /P supplied, del will prompt for individual files later.)
1185 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1186 if ((wcsstr(quals, L"/Q") == NULL) && (wcsstr(quals, L"/P") == NULL)) {
1187 WCHAR drive[10];
1188 WCHAR dir[MAX_PATH];
1189 WCHAR fname[MAX_PATH];
1190 WCHAR ext[MAX_PATH];
1191 WCHAR fpath[MAX_PATH];
1193 /* Convert path into actual directory spec */
1194 GetFullPathNameW(filename, ARRAY_SIZE(fpath), fpath, NULL);
1195 _wsplitpath(fpath, drive, dir, fname, ext);
1197 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1198 if ((lstrcmpW(fname, L"*") == 0) && (*ext == 0x00 || (lstrcmpW(ext, L".*") == 0))) {
1200 WCHAR question[MAXSTRING];
1202 /* Caller uses this to suppress "file not found" warning later */
1203 *pPrompted = TRUE;
1205 /* Ask for confirmation */
1206 wsprintfW(question, L"%s ", fpath);
1207 return WCMD_ask_confirm(question, TRUE, NULL);
1210 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1211 return TRUE;
1214 /* Helper function for WCMD_delete().
1215 * Deletes a single file, directory, or wildcard.
1216 * If /S was given, does it recursively.
1217 * Returns TRUE if a file was deleted.
1219 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1220 DWORD wanted_attrs;
1221 DWORD unwanted_attrs;
1222 BOOL found = FALSE;
1223 WCHAR argCopy[MAX_PATH];
1224 WIN32_FIND_DATAW fd;
1225 HANDLE hff;
1226 WCHAR fpath[MAX_PATH];
1227 WCHAR *p;
1228 BOOL handleParm = TRUE;
1230 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1232 lstrcpyW(argCopy, thisArg);
1233 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1234 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1236 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1237 /* Skip this arg if user declines to delete *.* */
1238 return FALSE;
1241 /* First, try to delete in the current directory */
1242 hff = FindFirstFileW(argCopy, &fd);
1243 if (hff == INVALID_HANDLE_VALUE) {
1244 handleParm = FALSE;
1245 } else {
1246 found = TRUE;
1249 /* Support del <dirname> by just deleting all files dirname\* */
1250 if (handleParm
1251 && (wcschr(argCopy,'*') == NULL)
1252 && (wcschr(argCopy,'?') == NULL)
1253 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1255 WCHAR modifiedParm[MAX_PATH];
1257 lstrcpyW(modifiedParm, argCopy);
1258 lstrcatW(modifiedParm, L"\\*");
1259 FindClose(hff);
1260 found = TRUE;
1261 WCMD_delete_one(modifiedParm);
1263 } else if (handleParm) {
1265 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1266 lstrcpyW (fpath, argCopy);
1267 do {
1268 p = wcsrchr (fpath, '\\');
1269 if (p != NULL) {
1270 *++p = '\0';
1271 lstrcatW (fpath, fd.cFileName);
1273 else lstrcpyW (fpath, fd.cFileName);
1274 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1275 BOOL ok;
1277 /* Handle attribute matching (/A) */
1278 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1279 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1281 /* /P means prompt for each file */
1282 if (ok && wcsstr(quals, L"/P") != NULL) {
1283 WCHAR* question;
1285 /* Ask for confirmation */
1286 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1287 ok = WCMD_ask_confirm(question, FALSE, NULL);
1288 LocalFree(question);
1291 /* Only proceed if ok to */
1292 if (ok) {
1294 /* If file is read only, and /A:r or /F supplied, delete it */
1295 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1296 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1297 wcsstr(quals, L"/F") != NULL)) {
1298 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1301 /* Now do the delete */
1302 if (!DeleteFileW(fpath)) WCMD_print_error ();
1306 } while (FindNextFileW(hff, &fd) != 0);
1307 FindClose (hff);
1310 /* Now recurse into all subdirectories handling the parameter in the same way */
1311 if (wcsstr(quals, L"/S") != NULL) {
1313 WCHAR thisDir[MAX_PATH];
1314 int cPos;
1316 WCHAR drive[10];
1317 WCHAR dir[MAX_PATH];
1318 WCHAR fname[MAX_PATH];
1319 WCHAR ext[MAX_PATH];
1321 /* Convert path into actual directory spec */
1322 GetFullPathNameW(argCopy, ARRAY_SIZE(thisDir), thisDir, NULL);
1323 _wsplitpath(thisDir, drive, dir, fname, ext);
1325 lstrcpyW(thisDir, drive);
1326 lstrcatW(thisDir, dir);
1327 cPos = lstrlenW(thisDir);
1329 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1331 /* Append '*' to the directory */
1332 thisDir[cPos] = '*';
1333 thisDir[cPos+1] = 0x00;
1335 hff = FindFirstFileW(thisDir, &fd);
1337 /* Remove residual '*' */
1338 thisDir[cPos] = 0x00;
1340 if (hff != INVALID_HANDLE_VALUE) {
1341 DIRECTORY_STACK *allDirs = NULL;
1342 DIRECTORY_STACK *lastEntry = NULL;
1344 do {
1345 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1346 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0)) {
1348 DIRECTORY_STACK *nextDir;
1349 WCHAR subParm[MAX_PATH];
1351 /* Work out search parameter in sub dir */
1352 lstrcpyW (subParm, thisDir);
1353 lstrcatW (subParm, fd.cFileName);
1354 lstrcatW (subParm, L"\\");
1355 lstrcatW (subParm, fname);
1356 lstrcatW (subParm, ext);
1357 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1359 /* Allocate memory, add to list */
1360 nextDir = heap_xalloc(sizeof(DIRECTORY_STACK));
1361 if (allDirs == NULL) allDirs = nextDir;
1362 if (lastEntry != NULL) lastEntry->next = nextDir;
1363 lastEntry = nextDir;
1364 nextDir->next = NULL;
1365 nextDir->dirName = heap_strdupW(subParm);
1367 } while (FindNextFileW(hff, &fd) != 0);
1368 FindClose (hff);
1370 /* Go through each subdir doing the delete */
1371 while (allDirs != NULL) {
1372 DIRECTORY_STACK *tempDir;
1374 tempDir = allDirs->next;
1375 found |= WCMD_delete_one (allDirs->dirName);
1377 heap_free(allDirs->dirName);
1378 heap_free(allDirs);
1379 allDirs = tempDir;
1384 return found;
1387 /****************************************************************************
1388 * WCMD_delete
1390 * Delete a file or wildcarded set.
1392 * Note on /A:
1393 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1394 * - Each set is a pattern, eg /ahr /as-r means
1395 * readonly+hidden OR nonreadonly system files
1396 * - The '-' applies to a single field, ie /a:-hr means read only
1397 * non-hidden files
1400 BOOL WCMD_delete (WCHAR *args) {
1401 int argno;
1402 WCHAR *argN;
1403 BOOL argsProcessed = FALSE;
1404 BOOL foundAny = FALSE;
1406 errorlevel = 0;
1408 for (argno=0; ; argno++) {
1409 BOOL found;
1410 WCHAR *thisArg;
1412 argN = NULL;
1413 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1414 if (!argN)
1415 break; /* no more parameters */
1416 if (argN[0] == '/')
1417 continue; /* skip options */
1419 argsProcessed = TRUE;
1420 found = WCMD_delete_one(thisArg);
1421 if (!found)
1422 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1423 foundAny |= found;
1426 /* Handle no valid args */
1427 if (!argsProcessed)
1428 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1430 return foundAny;
1434 * WCMD_strtrim
1436 * Returns a trimmed version of s with all leading and trailing whitespace removed
1437 * Pre: s non NULL
1440 static WCHAR *WCMD_strtrim(const WCHAR *s)
1442 DWORD len = lstrlenW(s);
1443 const WCHAR *start = s;
1444 WCHAR* result;
1446 result = heap_xalloc((len + 1) * sizeof(WCHAR));
1448 while (iswspace(*start)) start++;
1449 if (*start) {
1450 const WCHAR *end = s + len - 1;
1451 while (end > start && iswspace(*end)) end--;
1452 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1453 result[end - start + 1] = '\0';
1454 } else {
1455 result[0] = '\0';
1458 return result;
1461 /****************************************************************************
1462 * WCMD_echo
1464 * Echo input to the screen (or not). We don't try to emulate the bugs
1465 * in DOS (try typing "ECHO ON AGAIN" for an example).
1468 void WCMD_echo (const WCHAR *args)
1470 int count;
1471 const WCHAR *origcommand = args;
1472 WCHAR *trimmed;
1474 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1475 || args[0]==':' || args[0]==';' || args[0]=='/')
1476 args++;
1478 trimmed = WCMD_strtrim(args);
1479 if (!trimmed) return;
1481 count = lstrlenW(trimmed);
1482 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1483 && origcommand[0]!=';' && origcommand[0]!='/') {
1484 if (echo_mode) WCMD_output(WCMD_LoadMessage(WCMD_ECHOPROMPT), L"ON");
1485 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), L"OFF");
1486 heap_free(trimmed);
1487 return;
1490 if (lstrcmpiW(trimmed, L"ON") == 0)
1491 echo_mode = TRUE;
1492 else if (lstrcmpiW(trimmed, L"OFF") == 0)
1493 echo_mode = FALSE;
1494 else {
1495 WCMD_output_asis (args);
1496 WCMD_output_asis(L"\r\n");
1498 heap_free(trimmed);
1501 /*****************************************************************************
1502 * WCMD_part_execute
1504 * Execute a command, and any && or bracketed follow on to the command. The
1505 * first command to be executed may not be at the front of the
1506 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1508 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1509 BOOL isIF, BOOL executecmds)
1511 CMD_LIST *curPosition = *cmdList;
1512 int myDepth = (*cmdList)->bracketDepth;
1514 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d), isIF(%d)\n", cmdList,
1515 wine_dbgstr_w(firstcmd), executecmds, isIF);
1517 /* Skip leading whitespace between condition and the command */
1518 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1520 /* Process the first command, if there is one */
1521 if (executecmds && firstcmd && *firstcmd) {
1522 WCHAR *command = heap_strdupW(firstcmd);
1523 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1524 heap_free(command);
1528 /* If it didn't move the position, step to next command */
1529 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1531 /* Process any other parts of the command */
1532 if (*cmdList) {
1533 BOOL processThese = executecmds;
1535 while (*cmdList) {
1536 /* execute all appropriate commands */
1537 curPosition = *cmdList;
1539 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d) processThese(%d)\n",
1540 *cmdList,
1541 (*cmdList)->prevDelim,
1542 (*cmdList)->bracketDepth,
1543 myDepth,
1544 processThese);
1546 /* Execute any statements appended to the line */
1547 /* FIXME: Only if previous call worked for && or failed for || */
1548 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1549 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1550 if (processThese && (*cmdList)->command) {
1551 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1552 cmdList, FALSE);
1554 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1556 /* Execute any appended to the statement with (...) */
1557 } else if ((*cmdList)->bracketDepth > myDepth) {
1558 if (processThese) {
1559 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1560 } else {
1561 WINE_TRACE("Skipping command %p due to stack depth\n", *cmdList);
1563 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1565 /* End of the command - does 'ELSE ' follow as the next command? */
1566 } else {
1567 if (isIF && WCMD_keyword_ws_found(L"else", (*cmdList)->command)) {
1568 /* Swap between if and else processing */
1569 processThese = !executecmds;
1571 /* Process the ELSE part */
1572 if (processThese) {
1573 const int keyw_len = lstrlenW(L"else") + 1;
1574 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1576 /* Skip leading whitespace between condition and the command */
1577 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1578 if (*cmd) {
1579 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1581 } else {
1582 /* Loop skipping all commands until we get back to the current
1583 depth, including skipping commands and their subsequent
1584 pipes (eg cmd | prog) */
1585 do {
1586 *cmdList = (*cmdList)->nextcommand;
1587 } while (*cmdList &&
1588 ((*cmdList)->bracketDepth > myDepth ||
1589 (*cmdList)->prevDelim));
1591 /* After the else is complete, we need to now process subsequent commands */
1592 processThese = TRUE;
1594 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1596 /* If we were in an IF statement and we didn't find an else and yet we get back to
1597 the same bracket depth as the IF, then the IF statement is over. This is required
1598 to handle nested ifs properly */
1599 } else if (isIF && (*cmdList)->bracketDepth == myDepth) {
1600 if (WCMD_keyword_ws_found(L"do", (*cmdList)->command)) {
1601 WINE_TRACE("Still inside FOR-loop, not an end of IF statement\n");
1602 *cmdList = (*cmdList)->nextcommand;
1603 } else {
1604 WINE_TRACE("Found end of this nested IF statement, ending this if\n");
1605 break;
1607 } else if (!processThese) {
1608 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1609 WINE_TRACE("Skipping this command, as in not process mode (next = %p)\n", *cmdList);
1610 } else {
1611 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1612 break;
1617 return;
1620 /*****************************************************************************
1621 * WCMD_parse_forf_options
1623 * Parses the for /f 'options', extracting the values and validating the
1624 * keywords. Note all keywords are optional.
1625 * Parameters:
1626 * options [I] The unparsed parameter string
1627 * eol [O] Set to the comment character (eol=x)
1628 * skip [O] Set to the number of lines to skip (skip=xx)
1629 * delims [O] Set to the token delimiters (delims=)
1630 * tokens [O] Set to the requested tokens, as provided (tokens=)
1631 * usebackq [O] Set to TRUE if usebackq found
1633 * Returns TRUE on success, FALSE on syntax error
1636 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1637 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1640 WCHAR *pos = options;
1641 int len = lstrlenW(pos);
1642 const int eol_len = lstrlenW(L"eol=");
1643 const int skip_len = lstrlenW(L"skip=");
1644 const int tokens_len = lstrlenW(L"tokens=");
1645 const int delims_len = lstrlenW(L"delims=");
1646 const int usebackq_len = lstrlenW(L"usebackq");
1648 /* Initialize to defaults */
1649 lstrcpyW(delims, L" \t");
1650 lstrcpyW(tokens, L"1");
1651 *eol = 0;
1652 *skip = 0;
1653 *usebackq = FALSE;
1655 /* Strip (optional) leading and trailing quotes */
1656 if ((*pos == '"') && (pos[len-1] == '"')) {
1657 pos[len-1] = 0;
1658 pos++;
1661 /* Process each keyword */
1662 while (pos && *pos) {
1663 if (*pos == ' ' || *pos == '\t') {
1664 pos++;
1666 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1667 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1668 pos, eol_len, L"eol=", eol_len) == CSTR_EQUAL) {
1669 *eol = *(pos + eol_len);
1670 pos = pos + eol_len + 1;
1671 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1673 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1674 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1675 pos, skip_len, L"skip=", skip_len) == CSTR_EQUAL) {
1676 WCHAR *nextchar = NULL;
1677 pos = pos + skip_len;
1678 *skip = wcstoul(pos, &nextchar, 0);
1679 WINE_TRACE("Found skip as %d lines\n", *skip);
1680 pos = nextchar;
1682 /* Save if usebackq semantics are in effect */
1683 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, pos,
1684 usebackq_len, L"usebackq", usebackq_len) == CSTR_EQUAL) {
1685 *usebackq = TRUE;
1686 pos = pos + usebackq_len;
1687 WINE_TRACE("Found usebackq\n");
1689 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1690 if you finish the optionsroot string with delims= otherwise the space is
1691 just a token delimiter! */
1692 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1693 pos, delims_len, L"delims=", delims_len) == CSTR_EQUAL) {
1694 int i=0;
1696 pos = pos + delims_len;
1697 while (*pos && *pos != ' ') {
1698 delims[i++] = *pos;
1699 pos++;
1701 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1702 delims[i++] = 0; /* Null terminate the delims */
1703 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1705 /* Save the tokens being requested */
1706 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1707 pos, tokens_len, L"tokens=", tokens_len) == CSTR_EQUAL) {
1708 int i=0;
1710 pos = pos + tokens_len;
1711 while (*pos && *pos != ' ') {
1712 tokens[i++] = *pos;
1713 pos++;
1715 tokens[i++] = 0; /* Null terminate the tokens */
1716 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1718 } else {
1719 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1720 return FALSE;
1723 return TRUE;
1726 /*****************************************************************************
1727 * WCMD_add_dirstowalk
1729 * When recursing through directories (for /r), we need to add to the list of
1730 * directories still to walk, any subdirectories of the one we are processing.
1732 * Parameters
1733 * options [I] The remaining list of directories still to process
1735 * Note this routine inserts the subdirectories found between the entry being
1736 * processed, and any other directory still to be processed, mimicking what
1737 * Windows does
1739 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1740 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1741 WCHAR fullitem[MAX_PATH];
1742 WIN32_FIND_DATAW fd;
1743 HANDLE hff;
1745 /* Build a generic search and add all directories on the list of directories
1746 still to walk */
1747 lstrcpyW(fullitem, dirsToWalk->dirName);
1748 lstrcatW(fullitem, L"\\*");
1749 hff = FindFirstFileW(fullitem, &fd);
1750 if (hff != INVALID_HANDLE_VALUE) {
1751 do {
1752 WINE_TRACE("Looking for subdirectories\n");
1753 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1754 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
1756 /* Allocate memory, add to list */
1757 DIRECTORY_STACK *toWalk = heap_xalloc(sizeof(DIRECTORY_STACK));
1758 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1759 toWalk->next = remainingDirs->next;
1760 remainingDirs->next = toWalk;
1761 remainingDirs = toWalk;
1762 toWalk->dirName = heap_xalloc(sizeof(WCHAR) * (lstrlenW(dirsToWalk->dirName) + 2 + lstrlenW(fd.cFileName)));
1763 lstrcpyW(toWalk->dirName, dirsToWalk->dirName);
1764 lstrcatW(toWalk->dirName, L"\\");
1765 lstrcatW(toWalk->dirName, fd.cFileName);
1766 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1767 toWalk, toWalk->next);
1769 } while (FindNextFileW(hff, &fd) != 0);
1770 WINE_TRACE("Finished adding all subdirectories\n");
1771 FindClose (hff);
1775 /**************************************************************************
1776 * WCMD_for_nexttoken
1778 * Parse the token= line, identifying the next highest number not processed
1779 * so far. Count how many tokens are referred (including duplicates) and
1780 * optionally return that, plus optionally indicate if the tokens= line
1781 * ends in a star.
1783 * Parameters:
1784 * lasttoken [I] - Identifies the token index of the last one
1785 * returned so far (-1 used for first loop)
1786 * tokenstr [I] - The specified tokens= line
1787 * firstCmd [O] - Optionally indicate how many tokens are listed
1788 * doAll [O] - Optionally indicate if line ends with *
1789 * duplicates [O] - Optionally indicate if there is any evidence of
1790 * overlaying tokens in the string
1791 * Note the caller should keep a running track of duplicates as the tokens
1792 * are recursively passed. If any have duplicates, then the * token should
1793 * not be honoured.
1795 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1796 int *totalfound, BOOL *doall,
1797 BOOL *duplicates)
1799 WCHAR *pos = tokenstr;
1800 int nexttoken = -1;
1802 if (totalfound) *totalfound = 0;
1803 if (doall) *doall = FALSE;
1804 if (duplicates) *duplicates = FALSE;
1806 WINE_TRACE("Find next token after %d in %s\n", lasttoken,
1807 wine_dbgstr_w(tokenstr));
1809 /* Loop through the token string, parsing it. Valid syntax is:
1810 token=m or x-y with comma delimiter and optionally * to finish*/
1811 while (*pos) {
1812 int nextnumber1, nextnumber2 = -1;
1813 WCHAR *nextchar;
1815 /* Remember if the next character is a star, it indicates a need to
1816 show all remaining tokens and should be the last character */
1817 if (*pos == '*') {
1818 if (doall) *doall = TRUE;
1819 if (totalfound) (*totalfound)++;
1820 /* If we have not found a next token to return, then indicate
1821 time to process the star */
1822 if (nexttoken == -1) {
1823 if (lasttoken == -1) {
1824 /* Special case the syntax of tokens=* which just means get whole line */
1825 nexttoken = 0;
1826 } else {
1827 nexttoken = lasttoken;
1830 break;
1833 /* Get the next number */
1834 nextnumber1 = wcstoul(pos, &nextchar, 10);
1836 /* If it is followed by a minus, it's a range, so get the next one as well */
1837 if (*nextchar == '-') {
1838 nextnumber2 = wcstoul(nextchar+1, &nextchar, 10);
1840 /* We want to return the lowest number that is higher than lasttoken
1841 but only if range is positive */
1842 if (nextnumber2 >= nextnumber1 &&
1843 lasttoken < nextnumber2) {
1845 int nextvalue;
1846 if (nexttoken == -1) {
1847 nextvalue = max(nextnumber1, (lasttoken+1));
1848 } else {
1849 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1852 /* Flag if duplicates identified */
1853 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1855 nexttoken = nextvalue;
1858 /* Update the running total for the whole range */
1859 if (nextnumber2 >= nextnumber1 && totalfound) {
1860 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1862 pos = nextchar;
1864 } else if (pos != nextchar) {
1865 if (totalfound) (*totalfound)++;
1867 /* See if the number found is one we have already seen */
1868 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1870 /* We want to return the lowest number that is higher than lasttoken */
1871 if (lasttoken < nextnumber1 &&
1872 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1873 nexttoken = nextnumber1;
1875 pos = nextchar;
1877 } else {
1878 /* Step on to the next character, usually over comma */
1879 if (*pos) pos++;
1884 /* Return result */
1885 if (nexttoken == -1) {
1886 WINE_TRACE("No next token found, previous was %d\n", lasttoken);
1887 nexttoken = lasttoken;
1888 } else if (nexttoken==lasttoken && doall && *doall) {
1889 WINE_TRACE("Request for all remaining tokens now\n");
1890 } else {
1891 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1893 if (totalfound) WINE_TRACE("Found total tokens to be %d\n", *totalfound);
1894 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1895 return nexttoken;
1898 /**************************************************************************
1899 * WCMD_parse_line
1901 * When parsing file or string contents (for /f), once the string to parse
1902 * has been identified, handle the various options and call the do part
1903 * if appropriate.
1905 * Parameters:
1906 * cmdStart [I] - Identifies the list of commands making up the
1907 * for loop body (especially if brackets in use)
1908 * firstCmd [I] - The textual start of the command after the DO
1909 * which is within the first item of cmdStart
1910 * cmdEnd [O] - Identifies where to continue after the DO
1911 * variable [I] - The variable identified on the for line
1912 * buffer [I] - The string to parse
1913 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1914 * forf_skip [I/O] - How many lines to skip first
1915 * forf_eol [I] - The 'end of line' (comment) character
1916 * forf_delims [I] - The delimiters to use when breaking the string apart
1917 * forf_tokens [I] - The tokens to use when breaking the string apart
1919 static void WCMD_parse_line(CMD_LIST *cmdStart,
1920 const WCHAR *firstCmd,
1921 CMD_LIST **cmdEnd,
1922 const WCHAR variable,
1923 WCHAR *buffer,
1924 BOOL *doExecuted,
1925 int *forf_skip,
1926 WCHAR forf_eol,
1927 WCHAR *forf_delims,
1928 WCHAR *forf_tokens) {
1930 WCHAR *parm;
1931 FOR_CONTEXT oldcontext;
1932 int varidx, varoffset;
1933 int nexttoken, lasttoken = -1;
1934 BOOL starfound = FALSE;
1935 BOOL thisduplicate = FALSE;
1936 BOOL anyduplicates = FALSE;
1937 int totalfound;
1938 static WCHAR emptyW[] = L"";
1940 /* Skip lines if requested */
1941 if (*forf_skip) {
1942 (*forf_skip)--;
1943 return;
1946 /* Save away any existing for variable context (e.g. nested for loops) */
1947 oldcontext = forloopcontext;
1949 /* Extract the parameters based on the tokens= value (There will always
1950 be some value, as if it is not supplied, it defaults to tokens=1).
1951 Rough logic:
1952 Count how many tokens are named in the line, identify the lowest
1953 Empty (set to null terminated string) that number of named variables
1954 While lasttoken != nextlowest
1955 %letter = parameter number 'nextlowest'
1956 letter++ (if >26 or >52 abort)
1957 Go through token= string finding next lowest number
1958 If token ends in * set %letter = raw position of token(nextnumber+1)
1960 lasttoken = -1;
1961 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1962 &starfound, &thisduplicate);
1963 varidx = FOR_VAR_IDX(variable);
1965 /* Empty out variables */
1966 for (varoffset=0;
1967 varidx >= 0 && varoffset<totalfound && (((varidx%26) + varoffset) < 26);
1968 varoffset++) {
1969 forloopcontext.variable[varidx + varoffset] = emptyW;
1972 /* Loop extracting the tokens
1973 Note: nexttoken of 0 means there were no tokens requested, to handle
1974 the special case of tokens=* */
1975 varoffset = 0;
1976 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1977 while (varidx >= 0 && (nexttoken > 0 && (nexttoken > lasttoken))) {
1978 anyduplicates |= thisduplicate;
1980 /* Extract the token number requested and set into the next variable context */
1981 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims);
1982 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1983 varidx + varoffset, wine_dbgstr_w(parm));
1984 if (varidx >=0) {
1985 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1986 varoffset++;
1987 if (((varidx%26)+varoffset) >= 26) break;
1990 /* Find the next token */
1991 lasttoken = nexttoken;
1992 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1993 &starfound, &thisduplicate);
1996 /* If all the rest of the tokens were requested, and there is still space in
1997 the variable range, write them now */
1998 if (!anyduplicates && starfound && varidx >= 0 && (((varidx%26) + varoffset) < 26)) {
1999 nexttoken++;
2000 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
2001 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2002 varidx + varoffset, wine_dbgstr_w(parm));
2003 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2006 /* Execute the body of the foor loop with these values */
2007 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
2008 CMD_LIST *thisCmdStart = cmdStart;
2009 *doExecuted = TRUE;
2010 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
2011 *cmdEnd = thisCmdStart;
2014 /* Free the duplicated strings, and restore the context */
2015 if (varidx >=0) {
2016 int i;
2017 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
2018 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
2019 (forloopcontext.variable[i] != emptyW)) {
2020 heap_free(forloopcontext.variable[i]);
2025 /* Restore the original for variable contextx */
2026 forloopcontext = oldcontext;
2029 /**************************************************************************
2030 * WCMD_forf_getinputhandle
2032 * Return a file handle which can be used for reading the input lines,
2033 * either to a specific file (which may be quote delimited as we have to
2034 * read the parameters in raw mode) or to a command which we need to
2035 * execute. The command being executed runs in its own shell and stores
2036 * its data in a temporary file.
2038 * Parameters:
2039 * usebackq [I] - Indicates whether usebackq is in effect or not
2040 * itemStr [I] - The item to be handled, either a filename or
2041 * whole command string to execute
2042 * iscmd [I] - Identifies whether this is a command or not
2044 * Returns a file handle which can be used to read the input lines from.
2046 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2047 WCHAR temp_str[MAX_PATH];
2048 WCHAR temp_file[MAX_PATH];
2049 WCHAR temp_cmd[MAXSTRING];
2050 WCHAR *trimmed = NULL;
2051 HANDLE hinput = INVALID_HANDLE_VALUE;
2053 /* Remove leading and trailing character (but there may be trailing whitespace too) */
2054 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2055 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2056 (!iscmd && (itemstr[0] == '"' && usebackq)))
2058 trimmed = WCMD_strtrim(itemstr);
2059 if (trimmed) {
2060 itemstr = trimmed;
2062 itemstr[lstrlenW(itemstr)-1] = 0x00;
2063 itemstr++;
2066 if (iscmd) {
2067 /* Get temp filename */
2068 GetTempPathW(ARRAY_SIZE(temp_str), temp_str);
2069 GetTempFileNameW(temp_str, L"CMD", 0, temp_file);
2071 /* Redirect output to the temporary file */
2072 wsprintfW(temp_str, L">%s", temp_file);
2073 wsprintfW(temp_cmd, L"CMD.EXE /C %s", itemstr);
2074 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2075 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2076 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2078 /* Open the file, read line by line and process */
2079 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2080 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2082 } else {
2083 /* Open the file, read line by line and process */
2084 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2085 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2086 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2088 heap_free(trimmed);
2089 return hinput;
2092 /**************************************************************************
2093 * WCMD_for
2095 * Batch file loop processing.
2097 * On entry: cmdList contains the syntax up to the set
2098 * next cmdList and all in that bracket contain the set data
2099 * next cmdlist contains the DO cmd
2100 * following that is either brackets or && entries (as per if)
2104 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2106 WIN32_FIND_DATAW fd;
2107 HANDLE hff;
2108 int i;
2109 const int in_len = lstrlenW(L"in");
2110 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2111 WCHAR variable[4];
2112 int varidx = -1;
2113 WCHAR *oldvariablevalue;
2114 WCHAR *firstCmd;
2115 int thisDepth;
2116 WCHAR optionsRoot[MAX_PATH];
2117 DIRECTORY_STACK *dirsToWalk = NULL;
2118 BOOL expandDirs = FALSE;
2119 BOOL useNumbers = FALSE;
2120 BOOL doFileset = FALSE;
2121 BOOL doRecurse = FALSE;
2122 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2123 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2124 int itemNum;
2125 CMD_LIST *thisCmdStart;
2126 int parameterNo = 0;
2127 WCHAR forf_eol = 0;
2128 int forf_skip = 0;
2129 WCHAR forf_delims[256];
2130 WCHAR forf_tokens[MAXSTRING];
2131 BOOL forf_usebackq = FALSE;
2133 /* Handle optional qualifiers (multiple are allowed) */
2134 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2136 optionsRoot[0] = 0;
2137 while (thisArg && *thisArg == '/') {
2138 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2139 thisArg++;
2140 switch (towupper(*thisArg)) {
2141 case 'D': expandDirs = TRUE; break;
2142 case 'L': useNumbers = TRUE; break;
2144 /* Recursive is special case - /R can have an optional path following it */
2145 /* filenamesets are another special case - /F can have an optional options following it */
2146 case 'R':
2147 case 'F':
2149 /* When recursing directories, use current directory as the starting point unless
2150 subsequently overridden */
2151 doRecurse = (towupper(*thisArg) == 'R');
2152 if (doRecurse) GetCurrentDirectoryW(ARRAY_SIZE(optionsRoot), optionsRoot);
2154 doFileset = (towupper(*thisArg) == 'F');
2156 /* Retrieve next parameter to see if is root/options (raw form required
2157 with for /f, or unquoted in for /r) */
2158 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2160 /* Next parm is either qualifier, path/options or variable -
2161 only care about it if it is the path/options */
2162 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2163 parameterNo++;
2164 lstrcpyW(optionsRoot, thisArg);
2166 break;
2168 default:
2169 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2172 /* Step to next token */
2173 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2176 /* Ensure line continues with variable */
2177 if (*thisArg != '%') {
2178 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2179 return;
2182 /* With for /f parse the options if provided */
2183 if (doFileset) {
2184 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2185 forf_delims, forf_tokens, &forf_usebackq))
2187 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2188 return;
2191 /* Set up the list of directories to recurse if we are going to */
2192 } else if (doRecurse) {
2193 /* Allocate memory, add to list */
2194 dirsToWalk = heap_xalloc(sizeof(DIRECTORY_STACK));
2195 dirsToWalk->next = NULL;
2196 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2197 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2200 /* Variable should follow */
2201 lstrcpyW(variable, thisArg);
2202 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2203 varidx = FOR_VAR_IDX(variable[1]);
2205 /* Ensure line continues with IN */
2206 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2207 if (!thisArg
2208 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2209 thisArg, in_len, L"in", in_len) == CSTR_EQUAL)) {
2210 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2211 return;
2214 /* Save away where the set of data starts and the variable */
2215 thisDepth = (*cmdList)->bracketDepth;
2216 *cmdList = (*cmdList)->nextcommand;
2217 setStart = (*cmdList);
2219 /* Skip until the close bracket */
2220 WINE_TRACE("Searching %p as the set\n", *cmdList);
2221 while (*cmdList &&
2222 (*cmdList)->command != NULL &&
2223 (*cmdList)->bracketDepth > thisDepth) {
2224 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2225 *cmdList = (*cmdList)->nextcommand;
2228 /* Skip the close bracket, if there is one */
2229 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2231 /* Syntax error if missing close bracket, or nothing following it
2232 and once we have the complete set, we expect a DO */
2233 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2234 if ((*cmdList == NULL) || !WCMD_keyword_ws_found(L"do", (*cmdList)->command)) {
2235 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2236 return;
2239 cmdEnd = *cmdList;
2241 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2242 mode, or once for the rest of the time. */
2243 do {
2245 /* Save away the starting position for the commands (and offset for the
2246 first one) */
2247 cmdStart = *cmdList;
2248 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2249 itemNum = 0;
2251 /* If we are recursing directories (ie /R), add all sub directories now, then
2252 prefix the root when searching for the item */
2253 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2255 thisSet = setStart;
2256 /* Loop through all set entries */
2257 while (thisSet &&
2258 thisSet->command != NULL &&
2259 thisSet->bracketDepth >= thisDepth) {
2261 /* Loop through all entries on the same line */
2262 WCHAR *staticitem;
2263 WCHAR *itemStart;
2264 WCHAR buffer[MAXSTRING];
2266 WINE_TRACE("Processing for set %p\n", thisSet);
2267 i = 0;
2268 while (*(staticitem = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2271 * If the parameter within the set has a wildcard then search for matching files
2272 * otherwise do a literal substitution.
2275 /* Take a copy of the item returned from WCMD_parameter as it is held in a
2276 static buffer which can be overwritten during parsing of the for body */
2277 WCHAR item[MAXSTRING];
2278 lstrcpyW(item, staticitem);
2280 thisCmdStart = cmdStart;
2282 itemNum++;
2283 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2285 if (!useNumbers && !doFileset) {
2286 WCHAR fullitem[MAX_PATH];
2287 int prefixlen = 0;
2289 /* Now build the item to use / search for in the specified directory,
2290 as it is fully qualified in the /R case */
2291 if (dirsToWalk) {
2292 lstrcpyW(fullitem, dirsToWalk->dirName);
2293 lstrcatW(fullitem, L"\\");
2294 lstrcatW(fullitem, item);
2295 } else {
2296 WCHAR *prefix = wcsrchr(item, '\\');
2297 if (prefix) prefixlen = (prefix - item) + 1;
2298 lstrcpyW(fullitem, item);
2301 if (wcspbrk(fullitem, L"*?")) {
2302 hff = FindFirstFileW(fullitem, &fd);
2303 if (hff != INVALID_HANDLE_VALUE) {
2304 do {
2305 BOOL isDirectory = FALSE;
2307 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2309 /* Handle as files or dirs appropriately, but ignore . and .. */
2310 if (isDirectory == expandDirs &&
2311 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
2313 thisCmdStart = cmdStart;
2314 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2316 if (doRecurse) {
2317 lstrcpyW(fullitem, dirsToWalk->dirName);
2318 lstrcatW(fullitem, L"\\");
2319 lstrcatW(fullitem, fd.cFileName);
2320 } else {
2321 if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
2322 fullitem[prefixlen] = 0x00;
2323 lstrcatW(fullitem, fd.cFileName);
2325 doExecuted = TRUE;
2327 /* Save away any existing for variable context (e.g. nested for loops)
2328 and restore it after executing the body of this for loop */
2329 if (varidx >= 0) {
2330 oldvariablevalue = forloopcontext.variable[varidx];
2331 forloopcontext.variable[varidx] = fullitem;
2333 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2334 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2336 cmdEnd = thisCmdStart;
2338 } while (FindNextFileW(hff, &fd) != 0);
2339 FindClose (hff);
2341 } else {
2342 doExecuted = TRUE;
2344 /* Save away any existing for variable context (e.g. nested for loops)
2345 and restore it after executing the body of this for loop */
2346 if (varidx >= 0) {
2347 oldvariablevalue = forloopcontext.variable[varidx];
2348 forloopcontext.variable[varidx] = fullitem;
2350 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2351 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2353 cmdEnd = thisCmdStart;
2356 } else if (useNumbers) {
2357 /* Convert the first 3 numbers to signed longs and save */
2358 if (itemNum <=3) numbers[itemNum-1] = wcstol(item, NULL, 10);
2359 /* else ignore them! */
2361 /* Filesets - either a list of files, or a command to run and parse the output */
2362 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2363 (forf_usebackq && *itemStart != '\''))) {
2365 HANDLE input;
2366 WCHAR *itemparm;
2368 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2369 wine_dbgstr_w(item));
2371 /* If backquote or single quote, we need to launch that command
2372 and parse the results - use a temporary file */
2373 if ((forf_usebackq && *itemStart == '`') ||
2374 (!forf_usebackq && *itemStart == '\'')) {
2376 /* Use itemstart because the command is the whole set, not just the first token */
2377 itemparm = itemStart;
2378 } else {
2380 /* Use item because the file to process is just the first item in the set */
2381 itemparm = item;
2383 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2385 /* Process the input file */
2386 if (input == INVALID_HANDLE_VALUE) {
2387 WCMD_print_error ();
2388 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2389 errorlevel = 1;
2390 return; /* FOR loop aborts at first failure here */
2392 } else {
2394 /* Read line by line until end of file */
2395 while (WCMD_fgets(buffer, ARRAY_SIZE(buffer), input)) {
2396 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2397 &forf_skip, forf_eol, forf_delims, forf_tokens);
2398 buffer[0] = 0;
2400 CloseHandle (input);
2403 /* When we have processed the item as a whole command, abort future set processing */
2404 if (itemparm==itemStart) {
2405 thisSet = NULL;
2406 break;
2409 /* Filesets - A string literal */
2410 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2411 (forf_usebackq && *itemStart == '\''))) {
2413 /* Remove leading and trailing character, ready to parse with delims= delimiters
2414 Note that the last quote is removed from the set and the string terminates
2415 there to mimic windows */
2416 WCHAR *strend = wcsrchr(itemStart, forf_usebackq?'\'':'"');
2417 if (strend) {
2418 *strend = 0x00;
2419 itemStart++;
2422 /* Copy the item away from the global buffer used by WCMD_parameter */
2423 lstrcpyW(buffer, itemStart);
2424 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2425 &forf_skip, forf_eol, forf_delims, forf_tokens);
2427 /* Only one string can be supplied in the whole set, abort future set processing */
2428 thisSet = NULL;
2429 break;
2432 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2433 i++;
2436 /* Move onto the next set line */
2437 if (thisSet) thisSet = thisSet->nextcommand;
2440 /* If /L is provided, now run the for loop */
2441 if (useNumbers) {
2442 WCHAR thisNum[20];
2444 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2445 numbers[0], numbers[2], numbers[1]);
2446 for (i=numbers[0];
2447 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2448 i=i + numbers[1]) {
2450 swprintf(thisNum, ARRAY_SIZE(thisNum), L"%d", i);
2451 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2453 thisCmdStart = cmdStart;
2454 doExecuted = TRUE;
2456 /* Save away any existing for variable context (e.g. nested for loops)
2457 and restore it after executing the body of this for loop */
2458 if (varidx >= 0) {
2459 oldvariablevalue = forloopcontext.variable[varidx];
2460 forloopcontext.variable[varidx] = thisNum;
2462 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2463 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2465 cmdEnd = thisCmdStart;
2468 /* If we are walking directories, move on to any which remain */
2469 if (dirsToWalk != NULL) {
2470 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2471 heap_free(dirsToWalk->dirName);
2472 heap_free(dirsToWalk);
2473 dirsToWalk = nextDir;
2474 if (dirsToWalk) WINE_TRACE("Moving to next directory to iterate: %s\n",
2475 wine_dbgstr_w(dirsToWalk->dirName));
2476 else WINE_TRACE("Finished all directories.\n");
2479 } while (dirsToWalk != NULL);
2481 /* Now skip over the do part if we did not perform the for loop so far.
2482 We store in cmdEnd the next command after the do block, but we only
2483 know this if something was run. If it has not been, we need to calculate
2484 it. */
2485 if (!doExecuted) {
2486 thisCmdStart = cmdStart;
2487 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2488 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2489 cmdEnd = thisCmdStart;
2492 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2493 all processing, OR it should be pointing to the end of && processing OR
2494 it should be pointing at the NULL end of bracket for the DO. The return
2495 value needs to be the NEXT command to execute, which it either is, or
2496 we need to step over the closing bracket */
2497 *cmdList = cmdEnd;
2498 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2501 /**************************************************************************
2502 * WCMD_give_help
2504 * Simple on-line help. Help text is stored in the resource file.
2507 void WCMD_give_help (const WCHAR *args)
2509 size_t i;
2511 args = WCMD_skip_leading_spaces((WCHAR*) args);
2512 if (!*args) {
2513 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2515 else {
2516 /* Display help message for builtin commands */
2517 for (i=0; i<=WCMD_EXIT; i++) {
2518 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2519 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2520 WCMD_output_asis (WCMD_LoadMessage(i));
2521 return;
2524 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2525 for (i = 0; i <= ARRAY_SIZE(externals); i++) {
2526 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2527 args, -1, externals[i], -1) == CSTR_EQUAL) {
2528 WCHAR cmd[128];
2529 lstrcpyW(cmd, args);
2530 lstrcatW(cmd, L" /?");
2531 WCMD_run_program(cmd, FALSE);
2532 return;
2535 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2537 return;
2540 /****************************************************************************
2541 * WCMD_go_to
2543 * Batch file jump instruction. Not the most efficient algorithm ;-)
2544 * Prints error message if the specified label cannot be found - the file pointer is
2545 * then at EOF, effectively stopping the batch file.
2546 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2549 void WCMD_goto (CMD_LIST **cmdList) {
2551 WCHAR string[MAX_PATH];
2552 WCHAR *labelend = NULL;
2553 const WCHAR labelEndsW[] = L"><|& :\t";
2555 /* Do not process any more parts of a processed multipart or multilines command */
2556 if (cmdList) *cmdList = NULL;
2558 if (context != NULL) {
2559 WCHAR *paramStart = param1, *str;
2561 if (param1[0] == 0x00) {
2562 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2563 return;
2566 /* Handle special :EOF label */
2567 if (lstrcmpiW(L":eof", param1) == 0) {
2568 context -> skip_rest = TRUE;
2569 return;
2572 /* Support goto :label as well as goto label plus remove trailing chars */
2573 if (*paramStart == ':') paramStart++;
2574 labelend = wcspbrk(paramStart, labelEndsW);
2575 if (labelend) *labelend = 0x00;
2576 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
2578 /* Loop through potentially twice - once from current file position
2579 through to the end, and second time from start to current file
2580 position */
2581 if (*paramStart) {
2582 int loop;
2583 LARGE_INTEGER startli;
2584 for (loop=0; loop<2; loop++) {
2585 if (loop==0) {
2586 /* On first loop, save the file size */
2587 startli.QuadPart = 0;
2588 startli.u.LowPart = SetFilePointer(context -> h, startli.u.LowPart,
2589 &startli.u.HighPart, FILE_CURRENT);
2590 } else {
2591 /* On second loop, start at the beginning of the file */
2592 WINE_TRACE("Label not found, trying from beginning of file\n");
2593 if (loop==1) SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2596 while (WCMD_fgets (string, ARRAY_SIZE(string), context -> h)) {
2597 str = string;
2599 /* Ignore leading whitespace or no-echo character */
2600 while (*str=='@' || iswspace (*str)) str++;
2602 /* If the first real character is a : then this is a label */
2603 if (*str == ':') {
2604 str++;
2606 /* Skip spaces between : and label */
2607 while (iswspace (*str)) str++;
2608 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
2610 /* Label ends at whitespace or redirection characters */
2611 labelend = wcspbrk(str, labelEndsW);
2612 if (labelend) *labelend = 0x00;
2613 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
2615 if (lstrcmpiW (str, paramStart) == 0) return;
2618 /* See if we have gone beyond the end point if second time through */
2619 if (loop==1) {
2620 LARGE_INTEGER curli;
2621 curli.QuadPart = 0;
2622 curli.u.LowPart = SetFilePointer(context -> h, curli.u.LowPart,
2623 &curli.u.HighPart, FILE_CURRENT);
2624 if (curli.QuadPart > startli.QuadPart) {
2625 WINE_TRACE("Reached wrap point, label not found\n");
2626 break;
2633 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2634 context -> skip_rest = TRUE;
2636 return;
2639 /*****************************************************************************
2640 * WCMD_pushd
2642 * Push a directory onto the stack
2645 void WCMD_pushd (const WCHAR *args)
2647 struct env_stack *curdir;
2648 WCHAR *thisdir;
2650 if (wcschr(args, '/') != NULL) {
2651 SetLastError(ERROR_INVALID_PARAMETER);
2652 WCMD_print_error();
2653 return;
2656 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2657 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2658 if( !curdir || !thisdir ) {
2659 LocalFree(curdir);
2660 LocalFree(thisdir);
2661 WINE_ERR ("out of memory\n");
2662 return;
2665 /* Change directory using CD code with /D parameter */
2666 lstrcpyW(quals, L"/D");
2667 GetCurrentDirectoryW (1024, thisdir);
2668 errorlevel = 0;
2669 WCMD_setshow_default(args);
2670 if (errorlevel) {
2671 LocalFree(curdir);
2672 LocalFree(thisdir);
2673 return;
2674 } else {
2675 curdir -> next = pushd_directories;
2676 curdir -> strings = thisdir;
2677 if (pushd_directories == NULL) {
2678 curdir -> u.stackdepth = 1;
2679 } else {
2680 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2682 pushd_directories = curdir;
2687 /*****************************************************************************
2688 * WCMD_popd
2690 * Pop a directory from the stack
2693 void WCMD_popd (void) {
2694 struct env_stack *temp = pushd_directories;
2696 if (!pushd_directories)
2697 return;
2699 /* pop the old environment from the stack, and make it the current dir */
2700 pushd_directories = temp->next;
2701 SetCurrentDirectoryW(temp->strings);
2702 LocalFree (temp->strings);
2703 LocalFree (temp);
2706 /*******************************************************************
2707 * evaluate_if_comparison
2709 * Evaluates an "if" comparison operation
2711 * PARAMS
2712 * leftOperand [I] left operand, non NULL
2713 * operator [I] "if" binary comparison operator, non NULL
2714 * rightOperand [I] right operand, non NULL
2715 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2717 * RETURNS
2718 * Success: 1 if operator applied to the operands evaluates to TRUE
2719 * 0 if operator applied to the operands evaluates to FALSE
2720 * Failure: -1 if operator is not recognized
2722 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2723 const WCHAR *rightOperand, int caseInsensitive)
2725 WCHAR *endptr_leftOp, *endptr_rightOp;
2726 long int leftOperand_int, rightOperand_int;
2727 BOOL int_operands;
2729 /* == is a special case, as it always compares strings */
2730 if (!lstrcmpiW(operator, L"=="))
2731 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2732 : lstrcmpW (leftOperand, rightOperand) == 0;
2734 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2735 leftOperand_int = wcstol(leftOperand, &endptr_leftOp, 0);
2736 rightOperand_int = wcstol(rightOperand, &endptr_rightOp, 0);
2737 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2739 /* Perform actual (integer or string) comparison */
2740 if (!lstrcmpiW(operator, L"lss")) {
2741 if (int_operands)
2742 return leftOperand_int < rightOperand_int;
2743 else
2744 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2745 : lstrcmpW (leftOperand, rightOperand) < 0;
2748 if (!lstrcmpiW(operator, L"leq")) {
2749 if (int_operands)
2750 return leftOperand_int <= rightOperand_int;
2751 else
2752 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2753 : lstrcmpW (leftOperand, rightOperand) <= 0;
2756 if (!lstrcmpiW(operator, L"equ")) {
2757 if (int_operands)
2758 return leftOperand_int == rightOperand_int;
2759 else
2760 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2761 : lstrcmpW (leftOperand, rightOperand) == 0;
2764 if (!lstrcmpiW(operator, L"neq")) {
2765 if (int_operands)
2766 return leftOperand_int != rightOperand_int;
2767 else
2768 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2769 : lstrcmpW (leftOperand, rightOperand) != 0;
2772 if (!lstrcmpiW(operator, L"geq")) {
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"gtr")) {
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 return -1;
2791 int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate)
2793 WCHAR condition[MAX_PATH];
2794 int caseInsensitive = (wcsstr(quals, L"/I") != NULL);
2796 *negate = !lstrcmpiW(param1,L"not");
2797 lstrcpyW(condition, (*negate ? param2 : param1));
2798 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2800 if (!lstrcmpiW(condition, L"errorlevel")) {
2801 WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
2802 WCHAR *endptr;
2803 long int param_int = wcstol(param, &endptr, 10);
2804 if (*endptr) goto syntax_err;
2805 *test = ((long int)errorlevel >= param_int);
2806 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2808 else if (!lstrcmpiW(condition, L"exist")) {
2809 WIN32_FIND_DATAW fd;
2810 HANDLE hff;
2811 WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
2812 int len = lstrlenW(param);
2814 /* FindFirstFile does not like a directory path ending in '\', append a '.' */
2815 if (len && param[len-1] == '\\') lstrcatW(param, L".");
2817 hff = FindFirstFileW(param, &fd);
2818 *test = (hff != INVALID_HANDLE_VALUE );
2819 if (*test) FindClose(hff);
2821 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2823 else if (!lstrcmpiW(condition, L"defined")) {
2824 *test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE),
2825 NULL, 0) > 0);
2826 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2828 else { /* comparison operation */
2829 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2830 WCHAR *paramStart;
2832 lstrcpyW(leftOperand, WCMD_parameter(p, (*negate)+caseInsensitive, &paramStart, TRUE, FALSE));
2833 if (!*leftOperand)
2834 goto syntax_err;
2836 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2837 p = paramStart + lstrlenW(leftOperand);
2838 while (*p == ' ' || *p == '\t')
2839 p++;
2841 if (!wcsncmp(p, L"==", lstrlenW(L"==")))
2842 lstrcpyW(operator, L"==");
2843 else {
2844 lstrcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2845 if (!*operator) goto syntax_err;
2847 p += lstrlenW(operator);
2849 lstrcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2850 if (!*rightOperand)
2851 goto syntax_err;
2853 *test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2854 if (*test == -1)
2855 goto syntax_err;
2857 p = paramStart + lstrlenW(rightOperand);
2858 WCMD_parameter(p, 0, command, FALSE, FALSE);
2861 return 1;
2863 syntax_err:
2864 return -1;
2867 /****************************************************************************
2868 * WCMD_if
2870 * Batch file conditional.
2872 * On entry, cmdlist will point to command containing the IF, and optionally
2873 * the first command to execute (if brackets not found)
2874 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2875 * If ('s were found, execute all within that bracket
2876 * Command may optionally be followed by an ELSE - need to skip instructions
2877 * in the else using the same logic
2879 * FIXME: Much more syntax checking needed!
2881 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2883 int negate; /* Negate condition */
2884 int test; /* Condition evaluation result */
2885 WCHAR *command;
2887 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
2888 set in a call to WCMD_parse before */
2889 if (evaluate_if_condition(p, &command, &test, &negate) == -1)
2890 goto syntax_err;
2892 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n",
2893 wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
2894 wine_dbgstr_w(param2), wine_dbgstr_w(command));
2896 /* Process rest of IF statement which is on the same line
2897 Note: This may process all or some of the cmdList (eg a GOTO) */
2898 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2899 return;
2901 syntax_err:
2902 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2905 /****************************************************************************
2906 * WCMD_move
2908 * Move a file, directory tree or wildcarded set of files.
2911 void WCMD_move (void)
2913 BOOL status;
2914 WIN32_FIND_DATAW fd;
2915 HANDLE hff;
2916 WCHAR input[MAX_PATH];
2917 WCHAR output[MAX_PATH];
2918 WCHAR drive[10];
2919 WCHAR dir[MAX_PATH];
2920 WCHAR fname[MAX_PATH];
2921 WCHAR ext[MAX_PATH];
2923 if (param1[0] == 0x00) {
2924 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2925 return;
2928 /* If no destination supplied, assume current directory */
2929 if (param2[0] == 0x00) {
2930 lstrcpyW(param2, L".");
2933 /* If 2nd parm is directory, then use original filename */
2934 /* Convert partial path to full path */
2935 GetFullPathNameW(param1, ARRAY_SIZE(input), input, NULL);
2936 GetFullPathNameW(param2, ARRAY_SIZE(output), output, NULL);
2937 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2938 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2940 /* Split into components */
2941 _wsplitpath(input, drive, dir, fname, ext);
2943 hff = FindFirstFileW(input, &fd);
2944 if (hff == INVALID_HANDLE_VALUE)
2945 return;
2947 do {
2948 WCHAR dest[MAX_PATH];
2949 WCHAR src[MAX_PATH];
2950 DWORD attribs;
2951 BOOL ok = TRUE;
2952 DWORD flags = 0;
2954 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2956 /* Build src & dest name */
2957 lstrcpyW(src, drive);
2958 lstrcatW(src, dir);
2960 /* See if dest is an existing directory */
2961 attribs = GetFileAttributesW(output);
2962 if (attribs != INVALID_FILE_ATTRIBUTES &&
2963 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2964 lstrcpyW(dest, output);
2965 lstrcatW(dest, L"\\");
2966 lstrcatW(dest, fd.cFileName);
2967 } else {
2968 lstrcpyW(dest, output);
2971 lstrcatW(src, fd.cFileName);
2973 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2974 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2976 /* If destination exists, prompt unless /Y supplied */
2977 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2978 BOOL force = FALSE;
2979 WCHAR copycmd[MAXSTRING];
2980 DWORD len;
2982 /* Default whether automatic overwriting is on. If we are interactive then
2983 we prompt by default, otherwise we overwrite by default
2984 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2985 if (wcsstr(quals, L"/-Y"))
2986 force = FALSE;
2987 else if (wcsstr(quals, L"/Y"))
2988 force = TRUE;
2989 else {
2990 /* By default, we will force the overwrite in batch mode and ask for
2991 * confirmation in interactive mode. */
2992 force = !interactive;
2993 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
2994 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
2995 * default behavior. */
2996 len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
2997 if (len && len < ARRAY_SIZE(copycmd)) {
2998 if (!lstrcmpiW(copycmd, L"/Y"))
2999 force = TRUE;
3000 else if (!lstrcmpiW(copycmd, L"/-Y"))
3001 force = FALSE;
3005 /* Prompt if overwriting */
3006 if (!force) {
3007 WCHAR* question;
3009 /* Ask for confirmation */
3010 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
3011 ok = WCMD_ask_confirm(question, FALSE, NULL);
3012 LocalFree(question);
3015 if (ok)
3016 flags |= MOVEFILE_REPLACE_EXISTING;
3019 if (ok) {
3020 status = MoveFileExW(src, dest, flags);
3021 } else {
3022 status = TRUE;
3025 if (!status) {
3026 WCMD_print_error ();
3027 errorlevel = 1;
3029 } while (FindNextFileW(hff, &fd) != 0);
3031 FindClose(hff);
3034 /****************************************************************************
3035 * WCMD_pause
3037 * Suspend execution of a batch script until a key is typed
3040 void WCMD_pause (void)
3042 DWORD oldmode;
3043 BOOL have_console;
3044 DWORD count;
3045 WCHAR key;
3046 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
3048 have_console = GetConsoleMode(hIn, &oldmode);
3049 if (have_console)
3050 SetConsoleMode(hIn, 0);
3052 WCMD_output_asis(anykey);
3053 WCMD_ReadFile(hIn, &key, 1, &count);
3054 if (have_console)
3055 SetConsoleMode(hIn, oldmode);
3058 /****************************************************************************
3059 * WCMD_remove_dir
3061 * Delete a directory.
3064 void WCMD_remove_dir (WCHAR *args) {
3066 int argno = 0;
3067 int argsProcessed = 0;
3068 WCHAR *argN = args;
3070 /* Loop through all args */
3071 while (argN) {
3072 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3073 if (argN && argN[0] != '/') {
3074 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
3075 wine_dbgstr_w(quals));
3076 argsProcessed++;
3078 /* If subdirectory search not supplied, just try to remove
3079 and report error if it fails (eg if it contains a file) */
3080 if (wcsstr(quals, L"/S") == NULL) {
3081 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
3083 /* Otherwise use ShFileOp to recursively remove a directory */
3084 } else {
3086 SHFILEOPSTRUCTW lpDir;
3088 /* Ask first */
3089 if (wcsstr(quals, L"/Q") == NULL) {
3090 BOOL ok;
3091 WCHAR question[MAXSTRING];
3093 /* Ask for confirmation */
3094 wsprintfW(question, L"%s ", thisArg);
3095 ok = WCMD_ask_confirm(question, TRUE, NULL);
3097 /* Abort if answer is 'N' */
3098 if (!ok) return;
3101 /* Do the delete */
3102 lpDir.hwnd = NULL;
3103 lpDir.pTo = NULL;
3104 lpDir.pFrom = thisArg;
3105 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
3106 lpDir.wFunc = FO_DELETE;
3108 /* SHFileOperationW needs file list with a double null termination */
3109 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3111 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3116 /* Handle no valid args */
3117 if (argsProcessed == 0) {
3118 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3119 return;
3124 /****************************************************************************
3125 * WCMD_rename
3127 * Rename a file.
3130 void WCMD_rename (void)
3132 BOOL status;
3133 HANDLE hff;
3134 WIN32_FIND_DATAW fd;
3135 WCHAR input[MAX_PATH];
3136 WCHAR *dotDst = NULL;
3137 WCHAR drive[10];
3138 WCHAR dir[MAX_PATH];
3139 WCHAR fname[MAX_PATH];
3140 WCHAR ext[MAX_PATH];
3142 errorlevel = 0;
3144 /* Must be at least two args */
3145 if (param1[0] == 0x00 || param2[0] == 0x00) {
3146 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3147 errorlevel = 1;
3148 return;
3151 /* Destination cannot contain a drive letter or directory separator */
3152 if ((wcschr(param2,':') != NULL) || (wcschr(param2,'\\') != NULL)) {
3153 SetLastError(ERROR_INVALID_PARAMETER);
3154 WCMD_print_error();
3155 errorlevel = 1;
3156 return;
3159 /* Convert partial path to full path */
3160 GetFullPathNameW(param1, ARRAY_SIZE(input), input, NULL);
3161 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3162 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3163 dotDst = wcschr(param2, '.');
3165 /* Split into components */
3166 _wsplitpath(input, drive, dir, fname, ext);
3168 hff = FindFirstFileW(input, &fd);
3169 if (hff == INVALID_HANDLE_VALUE)
3170 return;
3172 do {
3173 WCHAR dest[MAX_PATH];
3174 WCHAR src[MAX_PATH];
3175 WCHAR *dotSrc = NULL;
3176 int dirLen;
3178 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3180 /* FIXME: If dest name or extension is *, replace with filename/ext
3181 part otherwise use supplied name. This supports:
3182 ren *.fred *.jim
3183 ren jim.* fred.* etc
3184 However, windows has a more complex algorithm supporting eg
3185 ?'s and *'s mid name */
3186 dotSrc = wcschr(fd.cFileName, '.');
3188 /* Build src & dest name */
3189 lstrcpyW(src, drive);
3190 lstrcatW(src, dir);
3191 lstrcpyW(dest, src);
3192 dirLen = lstrlenW(src);
3193 lstrcatW(src, fd.cFileName);
3195 /* Build name */
3196 if (param2[0] == '*') {
3197 lstrcatW(dest, fd.cFileName);
3198 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3199 } else {
3200 lstrcatW(dest, param2);
3201 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3204 /* Build Extension */
3205 if (dotDst && (*(dotDst+1)=='*')) {
3206 if (dotSrc) lstrcatW(dest, dotSrc);
3207 } else if (dotDst) {
3208 lstrcatW(dest, dotDst);
3211 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3212 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3214 status = MoveFileW(src, dest);
3216 if (!status) {
3217 WCMD_print_error ();
3218 errorlevel = 1;
3220 } while (FindNextFileW(hff, &fd) != 0);
3222 FindClose(hff);
3225 /*****************************************************************************
3226 * WCMD_dupenv
3228 * Make a copy of the environment.
3230 static WCHAR *WCMD_dupenv( const WCHAR *env )
3232 WCHAR *env_copy;
3233 int len;
3235 if( !env )
3236 return NULL;
3238 len = 0;
3239 while ( env[len] )
3240 len += (lstrlenW(&env[len]) + 1);
3242 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3243 if (!env_copy)
3245 WINE_ERR("out of memory\n");
3246 return env_copy;
3248 memcpy (env_copy, env, len*sizeof (WCHAR));
3249 env_copy[len] = 0;
3251 return env_copy;
3254 /*****************************************************************************
3255 * WCMD_setlocal
3257 * setlocal pushes the environment onto a stack
3258 * Save the environment as unicode so we don't screw anything up.
3260 void WCMD_setlocal (const WCHAR *s) {
3261 WCHAR *env;
3262 struct env_stack *env_copy;
3263 WCHAR cwd[MAX_PATH];
3264 BOOL newdelay;
3266 /* setlocal does nothing outside of batch programs */
3267 if (!context) return;
3269 /* DISABLEEXTENSIONS ignored */
3271 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3272 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3273 if (!wcsicmp(param1, L"ENABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"ENABLEDELAYEDEXPANSION")) {
3274 newdelay = TRUE;
3275 } else if (!wcsicmp(param1, L"DISABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"DISABLEDELAYEDEXPANSION")) {
3276 newdelay = FALSE;
3277 } else {
3278 newdelay = delayedsubst;
3280 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3282 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3283 if( !env_copy )
3285 WINE_ERR ("out of memory\n");
3286 return;
3289 env = GetEnvironmentStringsW ();
3290 env_copy->strings = WCMD_dupenv (env);
3291 if (env_copy->strings)
3293 env_copy->batchhandle = context->h;
3294 env_copy->next = saved_environment;
3295 env_copy->delayedsubst = delayedsubst;
3296 delayedsubst = newdelay;
3297 saved_environment = env_copy;
3299 /* Save the current drive letter */
3300 GetCurrentDirectoryW(MAX_PATH, cwd);
3301 env_copy->u.cwd = cwd[0];
3303 else
3304 LocalFree (env_copy);
3306 FreeEnvironmentStringsW (env);
3310 /*****************************************************************************
3311 * WCMD_endlocal
3313 * endlocal pops the environment off a stack
3314 * Note: When searching for '=', search from WCHAR position 1, to handle
3315 * special internal environment variables =C:, =D: etc
3317 void WCMD_endlocal (void) {
3318 WCHAR *env, *old, *p;
3319 struct env_stack *temp;
3320 int len, n;
3322 /* setlocal does nothing outside of batch programs */
3323 if (!context) return;
3325 /* setlocal needs a saved environment from within the same context (batch
3326 program) as it was saved in */
3327 if (!saved_environment || saved_environment->batchhandle != context->h)
3328 return;
3330 /* pop the old environment from the stack */
3331 temp = saved_environment;
3332 saved_environment = temp->next;
3334 /* delete the current environment, totally */
3335 env = GetEnvironmentStringsW ();
3336 old = WCMD_dupenv (env);
3337 len = 0;
3338 while (old[len]) {
3339 n = lstrlenW(&old[len]) + 1;
3340 p = wcschr(&old[len] + 1, '=');
3341 if (p)
3343 *p++ = 0;
3344 SetEnvironmentVariableW (&old[len], NULL);
3346 len += n;
3348 LocalFree (old);
3349 FreeEnvironmentStringsW (env);
3351 /* restore old environment */
3352 env = temp->strings;
3353 len = 0;
3354 delayedsubst = temp->delayedsubst;
3355 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3356 while (env[len]) {
3357 n = lstrlenW(&env[len]) + 1;
3358 p = wcschr(&env[len] + 1, '=');
3359 if (p)
3361 *p++ = 0;
3362 SetEnvironmentVariableW (&env[len], p);
3364 len += n;
3367 /* Restore current drive letter */
3368 if (IsCharAlphaW(temp->u.cwd)) {
3369 WCHAR envvar[4];
3370 WCHAR cwd[MAX_PATH];
3372 wsprintfW(envvar, L"=%c:", temp->u.cwd);
3373 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3374 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3375 SetCurrentDirectoryW(cwd);
3379 LocalFree (env);
3380 LocalFree (temp);
3383 /*****************************************************************************
3384 * WCMD_setshow_default
3386 * Set/Show the current default directory
3389 void WCMD_setshow_default (const WCHAR *args) {
3391 BOOL status;
3392 WCHAR string[1024];
3393 WCHAR cwd[1024];
3394 WCHAR *pos;
3395 WIN32_FIND_DATAW fd;
3396 HANDLE hff;
3398 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3400 /* Skip /D and trailing whitespace if on the front of the command line */
3401 if (lstrlenW(args) >= 2 &&
3402 CompareStringW(LOCALE_USER_DEFAULT,
3403 NORM_IGNORECASE | SORT_STRINGSORT,
3404 args, 2, L"/D", -1) == CSTR_EQUAL) {
3405 args += 2;
3406 while (*args && (*args==' ' || *args=='\t'))
3407 args++;
3410 GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
3412 if (!*args) {
3413 lstrcatW(cwd, L"\r\n");
3414 WCMD_output_asis (cwd);
3416 else {
3417 /* Remove any double quotes, which may be in the
3418 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3419 pos = string;
3420 while (*args) {
3421 if (*args != '"') *pos++ = *args;
3422 args++;
3424 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3425 pos--;
3426 *pos = 0x00;
3428 /* Search for appropriate directory */
3429 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3430 hff = FindFirstFileW(string, &fd);
3431 if (hff != INVALID_HANDLE_VALUE) {
3432 do {
3433 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3434 WCHAR fpath[MAX_PATH];
3435 WCHAR drive[10];
3436 WCHAR dir[MAX_PATH];
3437 WCHAR fname[MAX_PATH];
3438 WCHAR ext[MAX_PATH];
3440 /* Convert path into actual directory spec */
3441 GetFullPathNameW(string, ARRAY_SIZE(fpath), fpath, NULL);
3442 _wsplitpath(fpath, drive, dir, fname, ext);
3444 /* Rebuild path */
3445 wsprintfW(string, L"%s%s%s", drive, dir, fd.cFileName);
3446 break;
3448 } while (FindNextFileW(hff, &fd) != 0);
3449 FindClose(hff);
3452 /* Change to that directory */
3453 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3455 status = SetCurrentDirectoryW(string);
3456 if (!status) {
3457 errorlevel = 1;
3458 WCMD_print_error ();
3459 return;
3460 } else {
3462 /* Save away the actual new directory, to store as current location */
3463 GetCurrentDirectoryW(ARRAY_SIZE(string), string);
3465 /* Restore old directory if drive letter would change, and
3466 CD x:\directory /D (or pushd c:\directory) not supplied */
3467 if ((wcsstr(quals, L"/D") == NULL) &&
3468 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3469 SetCurrentDirectoryW(cwd);
3473 /* Set special =C: type environment variable, for drive letter of
3474 change of directory, even if path was restored due to missing
3475 /D (allows changing drive letter when not resident on that
3476 drive */
3477 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3478 WCHAR env[4];
3479 lstrcpyW(env, L"=");
3480 memcpy(env+1, string, 2 * sizeof(WCHAR));
3481 env[3] = 0x00;
3482 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3483 SetEnvironmentVariableW(env, string);
3487 return;
3490 /****************************************************************************
3491 * WCMD_setshow_date
3493 * Set/Show the system date
3494 * FIXME: Can't change date yet
3497 void WCMD_setshow_date (void) {
3499 WCHAR curdate[64], buffer[64];
3500 DWORD count;
3502 if (!*param1) {
3503 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, curdate, ARRAY_SIZE(curdate))) {
3504 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3505 if (wcsstr(quals, L"/T") == NULL) {
3506 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3507 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
3508 if (count > 2) {
3509 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3513 else WCMD_print_error ();
3515 else {
3516 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3520 /****************************************************************************
3521 * WCMD_compare
3522 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3523 * the equals sign.
3525 static int __cdecl WCMD_compare( const void *a, const void *b )
3527 int r;
3528 const WCHAR * const *str_a = a, * const *str_b = b;
3529 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3530 *str_a, wcscspn(*str_a, L"="), *str_b, wcscspn(*str_b, L"=") );
3531 if( r == CSTR_LESS_THAN ) return -1;
3532 if( r == CSTR_GREATER_THAN ) return 1;
3533 return 0;
3536 /****************************************************************************
3537 * WCMD_setshow_sortenv
3539 * sort variables into order for display
3540 * Optionally only display those who start with a stub
3541 * returns the count displayed
3543 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3545 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3546 const WCHAR **str;
3548 if (stub) stublen = lstrlenW(stub);
3550 /* count the number of strings, and the total length */
3551 while ( s[len] ) {
3552 len += (lstrlenW(&s[len]) + 1);
3553 count++;
3556 /* add the strings to an array */
3557 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3558 if( !str )
3559 return 0;
3560 str[0] = s;
3561 for( i=1; i<count; i++ )
3562 str[i] = str[i-1] + lstrlenW(str[i-1]) + 1;
3564 /* sort the array */
3565 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3567 /* print it */
3568 for( i=0; i<count; i++ ) {
3569 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3570 NORM_IGNORECASE | SORT_STRINGSORT,
3571 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3572 /* Don't display special internal variables */
3573 if (str[i][0] != '=') {
3574 WCMD_output_asis(str[i]);
3575 WCMD_output_asis(L"\r\n");
3576 displayedcount++;
3581 LocalFree( str );
3582 return displayedcount;
3585 /****************************************************************************
3586 * WCMD_getprecedence
3587 * Return the precedence of a particular operator
3589 static int WCMD_getprecedence(const WCHAR in)
3591 switch (in) {
3592 case '!':
3593 case '~':
3594 case OP_POSITIVE:
3595 case OP_NEGATIVE:
3596 return 8;
3597 case '*':
3598 case '/':
3599 case '%':
3600 return 7;
3601 case '+':
3602 case '-':
3603 return 6;
3604 case '<':
3605 case '>':
3606 return 5;
3607 case '&':
3608 return 4;
3609 case '^':
3610 return 3;
3611 case '|':
3612 return 2;
3613 case '=':
3614 case OP_ASSSIGNMUL:
3615 case OP_ASSSIGNDIV:
3616 case OP_ASSSIGNMOD:
3617 case OP_ASSSIGNADD:
3618 case OP_ASSSIGNSUB:
3619 case OP_ASSSIGNAND:
3620 case OP_ASSSIGNNOT:
3621 case OP_ASSSIGNOR:
3622 case OP_ASSSIGNSHL:
3623 case OP_ASSSIGNSHR:
3624 return 1;
3625 default:
3626 return 0;
3630 /****************************************************************************
3631 * WCMD_pushnumber
3632 * Push either a number or name (environment variable) onto the supplied
3633 * stack
3635 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3636 VARSTACK *thisstack = heap_xalloc(sizeof(VARSTACK));
3637 thisstack->isnum = (var == NULL);
3638 if (var) {
3639 thisstack->variable = var;
3640 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3641 } else {
3642 thisstack->value = num;
3643 WINE_TRACE("Pushed number %d\n", num);
3645 thisstack->next = *varstack;
3646 *varstack = thisstack;
3649 /****************************************************************************
3650 * WCMD_peeknumber
3651 * Returns the value of the top number or environment variable on the stack
3652 * and leaves the item on the stack.
3654 static int WCMD_peeknumber(VARSTACK **varstack) {
3655 int result = 0;
3656 VARSTACK *thisvar;
3658 if (varstack) {
3659 thisvar = *varstack;
3660 if (!thisvar->isnum) {
3661 WCHAR tmpstr[MAXSTRING];
3662 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3663 result = wcstol(tmpstr,NULL,0);
3665 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3666 } else {
3667 result = thisvar->value;
3670 WINE_TRACE("Peeked number %d\n", result);
3671 return result;
3674 /****************************************************************************
3675 * WCMD_popnumber
3676 * Returns the value of the top number or environment variable on the stack
3677 * and removes the item from the stack.
3679 static int WCMD_popnumber(VARSTACK **varstack) {
3680 int result = 0;
3681 VARSTACK *thisvar;
3683 if (varstack) {
3684 thisvar = *varstack;
3685 result = WCMD_peeknumber(varstack);
3686 if (!thisvar->isnum) heap_free(thisvar->variable);
3687 *varstack = thisvar->next;
3688 heap_free(thisvar);
3690 WINE_TRACE("Popped number %d\n", result);
3691 return result;
3694 /****************************************************************************
3695 * WCMD_pushoperator
3696 * Push an operator onto the supplied stack
3698 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3699 OPSTACK *thisstack = heap_xalloc(sizeof(OPSTACK));
3700 thisstack->precedence = precedence;
3701 thisstack->op = op;
3702 thisstack->next = *opstack;
3703 WINE_TRACE("Pushed operator %c\n", op);
3704 *opstack = thisstack;
3707 /****************************************************************************
3708 * WCMD_popoperator
3709 * Returns the operator from the top of the stack and removes the item from
3710 * the stack.
3712 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3713 WCHAR result = 0;
3714 OPSTACK *thisop;
3716 if (opstack) {
3717 thisop = *opstack;
3718 result = thisop->op;
3719 *opstack = thisop->next;
3720 heap_free(thisop);
3722 WINE_TRACE("Popped operator %c\n", result);
3723 return result;
3726 /****************************************************************************
3727 * WCMD_reduce
3728 * Actions the top operator on the stack against the first and sometimes
3729 * second value on the variable stack, and pushes the result
3730 * Returns non-zero on error.
3732 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3733 WCHAR thisop;
3734 int var1,var2;
3735 int rc = 0;
3737 if (!*opstack || !*varstack) {
3738 WINE_TRACE("No operators for the reduce\n");
3739 return WCMD_NOOPERATOR;
3742 /* Remove the top operator */
3743 thisop = WCMD_popoperator(opstack);
3744 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
3746 /* One variable operators */
3747 var1 = WCMD_popnumber(varstack);
3748 switch (thisop) {
3749 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3750 break;
3751 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3752 break;
3753 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3754 break;
3755 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3756 break;
3759 /* Two variable operators */
3760 if (!*varstack) {
3761 WINE_TRACE("No operands left for the reduce?\n");
3762 return WCMD_NOOPERAND;
3764 switch (thisop) {
3765 case '!':
3766 case '~':
3767 case OP_POSITIVE:
3768 case OP_NEGATIVE:
3769 break; /* Handled above */
3770 case '*': var2 = WCMD_popnumber(varstack);
3771 WCMD_pushnumber(NULL, var2*var1, varstack);
3772 break;
3773 case '/': var2 = WCMD_popnumber(varstack);
3774 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3775 WCMD_pushnumber(NULL, var2/var1, varstack);
3776 break;
3777 case '+': var2 = WCMD_popnumber(varstack);
3778 WCMD_pushnumber(NULL, var2+var1, varstack);
3779 break;
3780 case '-': var2 = WCMD_popnumber(varstack);
3781 WCMD_pushnumber(NULL, var2-var1, varstack);
3782 break;
3783 case '&': var2 = WCMD_popnumber(varstack);
3784 WCMD_pushnumber(NULL, var2&var1, varstack);
3785 break;
3786 case '%': var2 = WCMD_popnumber(varstack);
3787 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3788 WCMD_pushnumber(NULL, var2%var1, varstack);
3789 break;
3790 case '^': var2 = WCMD_popnumber(varstack);
3791 WCMD_pushnumber(NULL, var2^var1, varstack);
3792 break;
3793 case '<': var2 = WCMD_popnumber(varstack);
3794 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3795 which differs from the compiler (for example gcc) so being explicit. */
3796 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3797 WCMD_pushnumber(NULL, 0, varstack);
3798 } else {
3799 WCMD_pushnumber(NULL, var2<<var1, varstack);
3801 break;
3802 case '>': var2 = WCMD_popnumber(varstack);
3803 WCMD_pushnumber(NULL, var2>>var1, varstack);
3804 break;
3805 case '|': var2 = WCMD_popnumber(varstack);
3806 WCMD_pushnumber(NULL, var2|var1, varstack);
3807 break;
3809 case OP_ASSSIGNMUL:
3810 case OP_ASSSIGNDIV:
3811 case OP_ASSSIGNMOD:
3812 case OP_ASSSIGNADD:
3813 case OP_ASSSIGNSUB:
3814 case OP_ASSSIGNAND:
3815 case OP_ASSSIGNNOT:
3816 case OP_ASSSIGNOR:
3817 case OP_ASSSIGNSHL:
3818 case OP_ASSSIGNSHR:
3820 int i = 0;
3822 /* The left of an equals must be one variable */
3823 if (!(*varstack) || (*varstack)->isnum) {
3824 return WCMD_NOOPERAND;
3827 /* Make the number stack grow by inserting the value of the variable */
3828 var2 = WCMD_peeknumber(varstack);
3829 WCMD_pushnumber(NULL, var2, varstack);
3830 WCMD_pushnumber(NULL, var1, varstack);
3832 /* Make the operand stack grow by pushing the assign operator plus the
3833 operator to perform */
3834 while (calcassignments[i].op != ' ' &&
3835 calcassignments[i].calculatedop != thisop) {
3836 i++;
3838 if (calcassignments[i].calculatedop == ' ') {
3839 WINE_ERR("Unexpected operator %c\n", thisop);
3840 return WCMD_NOOPERATOR;
3842 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3843 WCMD_pushoperator(calcassignments[i].op,
3844 WCMD_getprecedence(calcassignments[i].op), opstack);
3845 break;
3848 case '=':
3850 WCHAR result[MAXSTRING];
3852 /* Build the result, then push it onto the stack */
3853 swprintf(result, ARRAY_SIZE(result), L"%d", var1);
3854 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3855 wine_dbgstr_w(result));
3856 SetEnvironmentVariableW((*varstack)->variable, result);
3857 var2 = WCMD_popnumber(varstack);
3858 WCMD_pushnumber(NULL, var1, varstack);
3859 break;
3862 default: WINE_ERR("Unrecognized operator %c\n", thisop);
3865 return rc;
3869 /****************************************************************************
3870 * WCMD_handleExpression
3871 * Handles an expression provided to set /a - If it finds brackets, it uses
3872 * recursion to process the parts in brackets.
3874 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3876 static const WCHAR mathDelims[] = L" \t()!~-*/%+<>&^|=,";
3877 int rc = 0;
3878 WCHAR *pos;
3879 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3880 OPSTACK *opstackhead = NULL;
3881 VARSTACK *varstackhead = NULL;
3882 WCHAR foundhalf = 0;
3884 /* Initialize */
3885 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3886 pos = *expr;
3888 /* Iterate through until whole expression is processed */
3889 while (pos && *pos) {
3890 BOOL treatasnumber;
3892 /* Skip whitespace to get to the next character to process*/
3893 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3894 if (!*pos) goto exprreturn;
3896 /* If we have found anything other than an operator then it's a number/variable */
3897 if (wcschr(mathDelims, *pos) == NULL) {
3898 WCHAR *parmstart, *parm, *dupparm;
3899 WCHAR *nextpos;
3901 /* Cannot have an expression with var/number twice, without an operator
3902 in-between, nor or number following a half constructed << or >> operator */
3903 if (lastwasnumber || foundhalf) {
3904 rc = WCMD_NOOPERATOR;
3905 goto exprerrorreturn;
3907 lastwasnumber = TRUE;
3909 if (iswdigit(*pos)) {
3910 /* For a number - just push it onto the stack */
3911 int num = wcstoul(pos, &nextpos, 0);
3912 WCMD_pushnumber(NULL, num, &varstackhead);
3913 pos = nextpos;
3915 /* Verify the number was validly formed */
3916 if (*nextpos && (wcschr(mathDelims, *nextpos) == NULL)) {
3917 rc = WCMD_BADHEXOCT;
3918 goto exprerrorreturn;
3920 } else {
3922 /* For a variable - just push it onto the stack */
3923 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3924 dupparm = heap_strdupW(parm);
3925 WCMD_pushnumber(dupparm, 0, &varstackhead);
3926 pos = parmstart + lstrlenW(dupparm);
3928 continue;
3931 /* We have found an operator. Some operators are one character, some two, and the minus
3932 and plus signs need special processing as they can be either operators or just influence
3933 the parameter which follows them */
3934 if (foundhalf && (*pos != foundhalf)) {
3935 /* Badly constructed operator pair */
3936 rc = WCMD_NOOPERATOR;
3937 goto exprerrorreturn;
3940 treatasnumber = FALSE; /* We are processing an operand */
3941 switch (*pos) {
3943 /* > and < are special as they are double character operators (and spaces can be between them!)
3944 If we see these for the first time, set a flag, and second time around we continue.
3945 Note these double character operators are stored as just one of the characters on the stack */
3946 case '>':
3947 case '<': if (!foundhalf) {
3948 foundhalf = *pos;
3949 pos++;
3950 break;
3952 /* We have found the rest, so clear up the knowledge of the half completed part and
3953 drop through to normal operator processing */
3954 foundhalf = 0;
3955 /* drop through */
3957 case '=': if (*pos=='=') {
3958 /* = is special cased as if the last was an operator then we may have e.g. += or
3959 *= etc which we need to handle by replacing the operator that is on the stack
3960 with a calculated assignment equivalent */
3961 if (!lastwasnumber && opstackhead) {
3962 int i = 0;
3963 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3964 i++;
3966 if (calcassignments[i].op == ' ') {
3967 rc = WCMD_NOOPERAND;
3968 goto exprerrorreturn;
3969 } else {
3970 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3971 when the general operator handling happens further down. */
3972 *pos = calcassignments[i].calculatedop;
3973 WCMD_popoperator(&opstackhead);
3977 /* Drop though */
3979 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3980 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3981 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
3982 /* drop through */
3983 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
3984 /* drop through */
3986 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3987 case '!': /* drop through */
3988 case '~': /* drop through */
3989 case '/': /* drop through */
3990 case '%': /* drop through */
3991 case '&': /* drop through */
3992 case '^': /* drop through */
3993 case '*': /* drop through */
3994 case '|':
3995 /* General code for handling most of the operators - look at the
3996 precedence of the top item on the stack, and see if we need to
3997 action the stack before we push something else onto it. */
3999 int precedence = WCMD_getprecedence(*pos);
4000 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
4001 precedence, !opstackhead?-1:opstackhead->precedence);
4003 /* In general, for things with the same precedence, reduce immediately
4004 except for assignments and unary operators which do not */
4005 while (!rc && opstackhead &&
4006 ((opstackhead->precedence > precedence) ||
4007 ((opstackhead->precedence == precedence) &&
4008 (precedence != 1) && (precedence != 8)))) {
4009 rc = WCMD_reduce(&opstackhead, &varstackhead);
4011 if (rc) goto exprerrorreturn;
4012 WCMD_pushoperator(*pos, precedence, &opstackhead);
4013 pos++;
4014 break;
4017 /* comma means start a new expression, ie calculate what we have */
4018 case ',':
4020 int prevresult = -1;
4021 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
4022 while (!rc && opstackhead) {
4023 rc = WCMD_reduce(&opstackhead, &varstackhead);
4025 if (rc) goto exprerrorreturn;
4026 /* If we have anything other than one number left, error
4027 otherwise throw the number away */
4028 if (!varstackhead || varstackhead->next) {
4029 rc = WCMD_NOOPERATOR;
4030 goto exprerrorreturn;
4032 prevresult = WCMD_popnumber(&varstackhead);
4033 WINE_TRACE("Expression resolved to %d\n", prevresult);
4034 heap_free(varstackhead);
4035 varstackhead = NULL;
4036 pos++;
4037 break;
4040 /* Open bracket - use iteration to parse the inner expression, then continue */
4041 case '(' : {
4042 int exprresult = 0;
4043 pos++;
4044 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
4045 if (rc) goto exprerrorreturn;
4046 WCMD_pushnumber(NULL, exprresult, &varstackhead);
4047 break;
4050 /* Close bracket - we have finished this depth, calculate and return */
4051 case ')' : {
4052 pos++;
4053 treatasnumber = TRUE; /* Things in brackets result in a number */
4054 if (depth == 0) {
4055 rc = WCMD_BADPAREN;
4056 goto exprerrorreturn;
4058 goto exprreturn;
4061 default:
4062 WINE_ERR("Unrecognized operator %c\n", *pos);
4063 pos++;
4065 lastwasnumber = treatasnumber;
4068 exprreturn:
4069 *expr = pos;
4071 /* We need to reduce until we have a single number (or variable) on the
4072 stack and set the return value to that */
4073 while (!rc && opstackhead) {
4074 rc = WCMD_reduce(&opstackhead, &varstackhead);
4076 if (rc) goto exprerrorreturn;
4078 /* If we have anything other than one number left, error
4079 otherwise throw the number away */
4080 if (!varstackhead || varstackhead->next) {
4081 rc = WCMD_NOOPERATOR;
4082 goto exprerrorreturn;
4085 /* Now get the number (and convert if it's just a variable name) */
4086 *ret = WCMD_popnumber(&varstackhead);
4088 exprerrorreturn:
4089 /* Free all remaining memory */
4090 while (opstackhead) WCMD_popoperator(&opstackhead);
4091 while (varstackhead) WCMD_popnumber(&varstackhead);
4093 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
4094 return rc;
4097 /****************************************************************************
4098 * WCMD_setshow_env
4100 * Set/Show the environment variables
4103 void WCMD_setshow_env (WCHAR *s) {
4105 LPVOID env;
4106 WCHAR *p;
4107 BOOL status;
4108 WCHAR string[MAXSTRING];
4110 if (param1[0] == 0x00 && quals[0] == 0x00) {
4111 env = GetEnvironmentStringsW();
4112 WCMD_setshow_sortenv( env, NULL );
4113 return;
4116 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4117 if (CompareStringW(LOCALE_USER_DEFAULT,
4118 NORM_IGNORECASE | SORT_STRINGSORT,
4119 s, 2, L"/P", -1) == CSTR_EQUAL) {
4120 DWORD count;
4122 s += 2;
4123 while (*s && (*s==' ' || *s=='\t')) s++;
4124 /* set /P "var=value"jim ignores anything after the last quote */
4125 if (*s=='\"') {
4126 WCHAR *lastquote;
4127 lastquote = WCMD_strip_quotes(s);
4128 if (lastquote) *lastquote = 0x00;
4129 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4132 /* If no parameter, or no '=' sign, return an error */
4133 if (!(*s) || ((p = wcschr (s, '=')) == NULL )) {
4134 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4135 return;
4138 /* Output the prompt */
4139 *p++ = '\0';
4140 if (*p) WCMD_output_asis(p);
4142 /* Read the reply */
4143 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
4144 if (count > 1) {
4145 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4146 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4147 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4148 wine_dbgstr_w(string));
4149 SetEnvironmentVariableW(s, string);
4152 /* See if /A supplied, and if so calculate the results of all the expressions */
4153 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4154 NORM_IGNORECASE | SORT_STRINGSORT,
4155 s, 2, L"/A", -1) == CSTR_EQUAL) {
4156 /* /A supplied, so evaluate expressions and set variables appropriately */
4157 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4158 /* of the final computation */
4159 int result = 0;
4160 int rc = 0;
4161 WCHAR *thisexpr;
4162 WCHAR *src,*dst;
4164 /* Remove all quotes before doing any calculations */
4165 thisexpr = heap_xalloc((lstrlenW(s+2)+1) * sizeof(WCHAR));
4166 src = s+2;
4167 dst = thisexpr;
4168 while (*src) {
4169 if (*src != '"') *dst++ = *src;
4170 src++;
4172 *dst = 0;
4174 /* Now calculate the results of the expression */
4175 src = thisexpr;
4176 rc = WCMD_handleExpression(&src, &result, 0);
4177 heap_free(thisexpr);
4179 /* If parsing failed, issue the error message */
4180 if (rc > 0) {
4181 WCMD_output_stderr(WCMD_LoadMessage(rc));
4182 return;
4185 /* If we have no context (interactive or cmd.exe /c) print the final result */
4186 if (!context) {
4187 swprintf(string, ARRAY_SIZE(string), L"%d", result);
4188 WCMD_output_asis(string);
4191 } else {
4192 DWORD gle;
4194 /* set "var=value"jim ignores anything after the last quote */
4195 if (*s=='\"') {
4196 WCHAR *lastquote;
4197 lastquote = WCMD_strip_quotes(s);
4198 if (lastquote) *lastquote = 0x00;
4199 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4202 p = wcschr (s, '=');
4203 if (p == NULL) {
4204 env = GetEnvironmentStringsW();
4205 if (WCMD_setshow_sortenv( env, s ) == 0) {
4206 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4207 errorlevel = 1;
4209 return;
4211 *p++ = '\0';
4213 if (!*p) p = NULL;
4214 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4215 wine_dbgstr_w(p));
4216 status = SetEnvironmentVariableW(s, p);
4217 gle = GetLastError();
4218 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4219 errorlevel = 1;
4220 } else if (!status) WCMD_print_error();
4221 else if (!interactive) errorlevel = 0;
4225 /****************************************************************************
4226 * WCMD_setshow_path
4228 * Set/Show the path environment variable
4231 void WCMD_setshow_path (const WCHAR *args) {
4233 WCHAR string[1024];
4234 DWORD status;
4236 if (!*param1 && !*param2) {
4237 status = GetEnvironmentVariableW(L"PATH", string, ARRAY_SIZE(string));
4238 if (status != 0) {
4239 WCMD_output_asis(L"PATH=");
4240 WCMD_output_asis ( string);
4241 WCMD_output_asis(L"\r\n");
4243 else {
4244 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4247 else {
4248 if (*args == '=') args++; /* Skip leading '=' */
4249 status = SetEnvironmentVariableW(L"PATH", args);
4250 if (!status) WCMD_print_error();
4254 /****************************************************************************
4255 * WCMD_setshow_prompt
4257 * Set or show the command prompt.
4260 void WCMD_setshow_prompt (void) {
4262 WCHAR *s;
4264 if (!*param1) {
4265 SetEnvironmentVariableW(L"PROMPT", NULL);
4267 else {
4268 s = param1;
4269 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4270 if (!*s) {
4271 SetEnvironmentVariableW(L"PROMPT", NULL);
4273 else SetEnvironmentVariableW(L"PROMPT", s);
4277 /****************************************************************************
4278 * WCMD_setshow_time
4280 * Set/Show the system time
4281 * FIXME: Can't change time yet
4284 void WCMD_setshow_time (void) {
4286 WCHAR curtime[64], buffer[64];
4287 DWORD count;
4288 SYSTEMTIME st;
4290 if (!*param1) {
4291 GetLocalTime(&st);
4292 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, curtime, ARRAY_SIZE(curtime))) {
4293 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4294 if (wcsstr(quals, L"/T") == NULL) {
4295 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4296 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
4297 if (count > 2) {
4298 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4302 else WCMD_print_error ();
4304 else {
4305 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4309 /****************************************************************************
4310 * WCMD_shift
4312 * Shift batch parameters.
4313 * Optional /n says where to start shifting (n=0-8)
4316 void WCMD_shift (const WCHAR *args) {
4317 int start;
4319 if (context != NULL) {
4320 WCHAR *pos = wcschr(args, '/');
4321 int i;
4323 if (pos == NULL) {
4324 start = 0;
4325 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4326 start = (*(pos+1) - '0');
4327 } else {
4328 SetLastError(ERROR_INVALID_PARAMETER);
4329 WCMD_print_error();
4330 return;
4333 WINE_TRACE("Shifting variables, starting at %d\n", start);
4334 for (i=start;i<=8;i++) {
4335 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4337 context -> shift_count[9] = context -> shift_count[9] + 1;
4342 /****************************************************************************
4343 * WCMD_start
4345 void WCMD_start(WCHAR *args)
4347 int argno;
4348 int have_title;
4349 WCHAR file[MAX_PATH];
4350 WCHAR *cmdline, *cmdline_params;
4351 STARTUPINFOW st;
4352 PROCESS_INFORMATION pi;
4354 GetWindowsDirectoryW( file, MAX_PATH );
4355 lstrcatW(file, L"\\command\\start.exe");
4356 cmdline = heap_xalloc( (lstrlenW(file) + lstrlenW(args) + 8) * sizeof(WCHAR) );
4357 lstrcpyW( cmdline, file );
4358 lstrcatW(cmdline, L" ");
4359 cmdline_params = cmdline + lstrlenW(cmdline);
4361 /* The start built-in has some special command-line parsing properties
4362 * which will be outlined here.
4364 * both '\t' and ' ' are argument separators
4365 * '/' has a special double role as both separator and switch prefix, e.g.
4367 * > start /low/i
4368 * or
4369 * > start "title"/i
4371 * are valid ways to pass multiple options to start. In the latter case
4372 * '/i' is not a part of the title but parsed as a switch.
4374 * However, '=', ';' and ',' are not separators:
4375 * > start "deus"=ex,machina
4377 * will in fact open a console titled 'deus=ex,machina'
4379 * The title argument parsing code is only interested in quotes themselves,
4380 * it does not respect escaping of any kind and all quotes are dropped
4381 * from the resulting title, therefore:
4383 * > start "\"" hello"/low
4385 * actually opens a console titled '\ hello' with low priorities.
4387 * To not break compatibility with wine programs relying on
4388 * wine's separate 'start.exe', this program's peculiar console
4389 * title parsing is actually implemented in 'cmd.exe' which is the
4390 * application native Windows programs will use to invoke 'start'.
4392 * WCMD_parameter_with_delims will take care of everything for us.
4394 have_title = FALSE;
4395 for (argno=0; ; argno++) {
4396 WCHAR *thisArg, *argN;
4398 argN = NULL;
4399 thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, L" \t/");
4401 /* No more parameters */
4402 if (!argN)
4403 break;
4405 /* Found the title */
4406 if (argN[0] == '"') {
4407 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
4408 have_title = TRUE;
4410 /* Copy all of the cmdline processed */
4411 memcpy(cmdline_params, args, sizeof(WCHAR) * (argN - args));
4412 cmdline_params[argN - args] = '\0';
4414 /* Add quoted title */
4415 lstrcatW(cmdline_params, L"\"\\\"");
4416 lstrcatW(cmdline_params, thisArg);
4417 lstrcatW(cmdline_params, L"\\\"\"");
4419 /* Concatenate remaining command-line */
4420 thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, L" \t/");
4421 lstrcatW(cmdline_params, argN + lstrlenW(thisArg));
4423 break;
4426 /* Skipping a regular argument? */
4427 else if (argN != args && argN[-1] == '/') {
4428 continue;
4430 /* Not an argument nor the title, start of program arguments,
4431 * stop looking for title.
4433 } else
4434 break;
4437 /* build command-line if not built yet */
4438 if (!have_title) {
4439 lstrcatW( cmdline, args );
4442 memset( &st, 0, sizeof(STARTUPINFOW) );
4443 st.cb = sizeof(STARTUPINFOW);
4445 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4447 WaitForSingleObject( pi.hProcess, INFINITE );
4448 GetExitCodeProcess( pi.hProcess, &errorlevel );
4449 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4450 CloseHandle(pi.hProcess);
4451 CloseHandle(pi.hThread);
4453 else
4455 SetLastError(ERROR_FILE_NOT_FOUND);
4456 WCMD_print_error ();
4457 errorlevel = 9009;
4459 heap_free(cmdline);
4462 /****************************************************************************
4463 * WCMD_title
4465 * Set the console title
4467 void WCMD_title (const WCHAR *args) {
4468 SetConsoleTitleW(args);
4471 /****************************************************************************
4472 * WCMD_type
4474 * Copy a file to standard output.
4477 void WCMD_type (WCHAR *args) {
4479 int argno = 0;
4480 WCHAR *argN = args;
4481 BOOL writeHeaders = FALSE;
4483 if (param1[0] == 0x00) {
4484 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4485 return;
4488 if (param2[0] != 0x00) writeHeaders = TRUE;
4490 /* Loop through all args */
4491 errorlevel = 0;
4492 while (argN) {
4493 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4495 HANDLE h;
4496 WCHAR buffer[512];
4497 DWORD count;
4499 if (!argN) break;
4501 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4502 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4503 FILE_ATTRIBUTE_NORMAL, NULL);
4504 if (h == INVALID_HANDLE_VALUE) {
4505 WCMD_print_error ();
4506 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4507 errorlevel = 1;
4508 } else {
4509 if (writeHeaders) {
4510 WCMD_output_stderr(L"\n%1\n\n\n", thisArg);
4512 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer) - 1, &count)) {
4513 if (count == 0) break; /* ReadFile reports success on EOF! */
4514 buffer[count] = 0;
4515 WCMD_output_asis (buffer);
4517 CloseHandle (h);
4522 /****************************************************************************
4523 * WCMD_more
4525 * Output either a file or stdin to screen in pages
4528 void WCMD_more (WCHAR *args) {
4530 int argno = 0;
4531 WCHAR *argN = args;
4532 WCHAR moreStr[100];
4533 WCHAR moreStrPage[100];
4534 WCHAR buffer[512];
4535 DWORD count;
4537 /* Prefix the NLS more with '-- ', then load the text */
4538 errorlevel = 0;
4539 lstrcpyW(moreStr, L"-- ");
4540 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3], ARRAY_SIZE(moreStr)-3);
4542 if (param1[0] == 0x00) {
4544 /* Wine implements pipes via temporary files, and hence stdin is
4545 effectively reading from the file. This means the prompts for
4546 more are satisfied by the next line from the input (file). To
4547 avoid this, ensure stdin is to the console */
4548 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4549 HANDLE hConIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
4550 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4551 FILE_ATTRIBUTE_NORMAL, 0);
4552 WINE_TRACE("No parms - working probably in pipe mode\n");
4553 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4555 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4556 once you get in this bit unless due to a pipe, it's going to end badly... */
4557 wsprintfW(moreStrPage, L"%s --\n", moreStr);
4559 WCMD_enter_paged_mode(moreStrPage);
4560 while (WCMD_ReadFile(hstdin, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4561 if (count == 0) break; /* ReadFile reports success on EOF! */
4562 buffer[count] = 0;
4563 WCMD_output_asis (buffer);
4565 WCMD_leave_paged_mode();
4567 /* Restore stdin to what it was */
4568 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4569 CloseHandle(hConIn);
4571 return;
4572 } else {
4573 BOOL needsPause = FALSE;
4575 /* Loop through all args */
4576 WINE_TRACE("Parms supplied - working through each file\n");
4577 WCMD_enter_paged_mode(moreStrPage);
4579 while (argN) {
4580 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4581 HANDLE h;
4583 if (!argN) break;
4585 if (needsPause) {
4587 /* Wait */
4588 wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, 100);
4589 WCMD_leave_paged_mode();
4590 WCMD_output_asis(moreStrPage);
4591 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
4592 WCMD_enter_paged_mode(moreStrPage);
4596 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4597 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4598 FILE_ATTRIBUTE_NORMAL, NULL);
4599 if (h == INVALID_HANDLE_VALUE) {
4600 WCMD_print_error ();
4601 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4602 errorlevel = 1;
4603 } else {
4604 ULONG64 curPos = 0;
4605 ULONG64 fileLen = 0;
4606 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4608 /* Get the file size */
4609 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4610 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4612 needsPause = TRUE;
4613 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4614 if (count == 0) break; /* ReadFile reports success on EOF! */
4615 buffer[count] = 0;
4616 curPos += count;
4618 /* Update % count (would be used in WCMD_output_asis as prompt) */
4619 wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, (int) min(99, (curPos * 100)/fileLen));
4621 WCMD_output_asis (buffer);
4623 CloseHandle (h);
4627 WCMD_leave_paged_mode();
4631 /****************************************************************************
4632 * WCMD_verify
4634 * Display verify flag.
4635 * FIXME: We don't actually do anything with the verify flag other than toggle
4636 * it...
4639 void WCMD_verify (const WCHAR *args) {
4641 int count;
4643 count = lstrlenW(args);
4644 if (count == 0) {
4645 if (verify_mode) WCMD_output(WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"ON");
4646 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"OFF");
4647 return;
4649 if (lstrcmpiW(args, L"ON") == 0) {
4650 verify_mode = TRUE;
4651 return;
4653 else if (lstrcmpiW(args, L"OFF") == 0) {
4654 verify_mode = FALSE;
4655 return;
4657 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4660 /****************************************************************************
4661 * WCMD_version
4663 * Display version info.
4666 void WCMD_version (void) {
4668 WCMD_output_asis (version_string);
4672 /****************************************************************************
4673 * WCMD_volume
4675 * Display volume information (set_label = FALSE)
4676 * Additionally set volume label (set_label = TRUE)
4677 * Returns 1 on success, 0 otherwise
4680 int WCMD_volume(BOOL set_label, const WCHAR *path)
4682 DWORD count, serial;
4683 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4684 BOOL status;
4686 if (!*path) {
4687 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
4688 if (!status) {
4689 WCMD_print_error ();
4690 return 0;
4692 status = GetVolumeInformationW(NULL, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4694 else {
4695 if ((path[1] != ':') || (lstrlenW(path) != 2)) {
4696 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4697 return 0;
4699 wsprintfW (curdir, L"%s\\", path);
4700 status = GetVolumeInformationW(curdir, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4702 if (!status) {
4703 WCMD_print_error ();
4704 return 0;
4706 if (label[0] != '\0') {
4707 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4708 curdir[0], label);
4710 else {
4711 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4712 curdir[0]);
4714 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4715 HIWORD(serial), LOWORD(serial));
4716 if (set_label) {
4717 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4718 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
4719 if (count > 1) {
4720 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4721 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4723 if (*path) {
4724 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4726 else {
4727 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4730 return 1;
4733 /**************************************************************************
4734 * WCMD_exit
4736 * Exit either the process, or just this batch program
4740 void WCMD_exit (CMD_LIST **cmdList) {
4741 int rc = wcstol(param1, NULL, 10); /* Note: wcstol of empty parameter is 0 */
4743 if (context && lstrcmpiW(quals, L"/B") == 0) {
4744 errorlevel = rc;
4745 context -> skip_rest = TRUE;
4746 *cmdList = NULL;
4747 } else {
4748 ExitProcess(rc);
4753 /*****************************************************************************
4754 * WCMD_assoc
4756 * Lists or sets file associations (assoc = TRUE)
4757 * Lists or sets file types (assoc = FALSE)
4759 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4761 HKEY key;
4762 DWORD accessOptions = KEY_READ;
4763 WCHAR *newValue;
4764 LONG rc = ERROR_SUCCESS;
4765 WCHAR keyValue[MAXSTRING];
4766 DWORD valueLen = MAXSTRING;
4767 HKEY readKey;
4769 /* See if parameter includes '=' */
4770 errorlevel = 0;
4771 newValue = wcschr(args, '=');
4772 if (newValue) accessOptions |= KEY_WRITE;
4774 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4775 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, L"", 0, accessOptions, &key) != ERROR_SUCCESS) {
4776 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4777 return;
4780 /* If no parameters then list all associations */
4781 if (*args == 0x00) {
4782 int index = 0;
4784 /* Enumerate all the keys */
4785 while (rc != ERROR_NO_MORE_ITEMS) {
4786 WCHAR keyName[MAXSTRING];
4787 DWORD nameLen;
4789 /* Find the next value */
4790 nameLen = MAXSTRING;
4791 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4793 if (rc == ERROR_SUCCESS) {
4795 /* Only interested in extension ones if assoc, or others
4796 if not assoc */
4797 if ((keyName[0] == '.' && assoc) ||
4798 (!(keyName[0] == '.') && (!assoc)))
4800 WCHAR subkey[MAXSTRING];
4801 lstrcpyW(subkey, keyName);
4802 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4804 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4806 valueLen = ARRAY_SIZE(keyValue);
4807 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4808 WCMD_output_asis(keyName);
4809 WCMD_output_asis(L"=");
4810 /* If no default value found, leave line empty after '=' */
4811 if (rc == ERROR_SUCCESS) {
4812 WCMD_output_asis(keyValue);
4814 WCMD_output_asis(L"\r\n");
4815 RegCloseKey(readKey);
4821 } else {
4823 /* Parameter supplied - if no '=' on command line, it's a query */
4824 if (newValue == NULL) {
4825 WCHAR *space;
4826 WCHAR subkey[MAXSTRING];
4828 /* Query terminates the parameter at the first space */
4829 lstrcpyW(keyValue, args);
4830 space = wcschr(keyValue, ' ');
4831 if (space) *space=0x00;
4833 /* Set up key name */
4834 lstrcpyW(subkey, keyValue);
4835 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4837 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4839 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4840 WCMD_output_asis(args);
4841 WCMD_output_asis(L"=");
4842 /* If no default value found, leave line empty after '=' */
4843 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4844 WCMD_output_asis(L"\r\n");
4845 RegCloseKey(readKey);
4847 } else {
4848 WCHAR msgbuffer[MAXSTRING];
4850 /* Load the translated 'File association not found' */
4851 if (assoc) {
4852 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4853 } else {
4854 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4856 WCMD_output_stderr(msgbuffer, keyValue);
4857 errorlevel = 2;
4860 /* Not a query - it's a set or clear of a value */
4861 } else {
4863 WCHAR subkey[MAXSTRING];
4865 /* Get pointer to new value */
4866 *newValue = 0x00;
4867 newValue++;
4869 /* Set up key name */
4870 lstrcpyW(subkey, args);
4871 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4873 /* If nothing after '=' then clear value - only valid for ASSOC */
4874 if (*newValue == 0x00) {
4876 if (assoc) rc = RegDeleteKeyW(key, args);
4877 if (assoc && rc == ERROR_SUCCESS) {
4878 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4880 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4881 WCMD_print_error();
4882 errorlevel = 2;
4884 } else {
4885 WCHAR msgbuffer[MAXSTRING];
4887 /* Load the translated 'File association not found' */
4888 if (assoc) {
4889 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4890 } else {
4891 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4893 WCMD_output_stderr(msgbuffer, args);
4894 errorlevel = 2;
4897 /* It really is a set value = contents */
4898 } else {
4899 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4900 accessOptions, NULL, &readKey, NULL);
4901 if (rc == ERROR_SUCCESS) {
4902 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4903 (LPBYTE)newValue,
4904 sizeof(WCHAR) * (lstrlenW(newValue) + 1));
4905 RegCloseKey(readKey);
4908 if (rc != ERROR_SUCCESS) {
4909 WCMD_print_error();
4910 errorlevel = 2;
4911 } else {
4912 WCMD_output_asis(args);
4913 WCMD_output_asis(L"=");
4914 WCMD_output_asis(newValue);
4915 WCMD_output_asis(L"\r\n");
4921 /* Clean up */
4922 RegCloseKey(key);
4925 /****************************************************************************
4926 * WCMD_color
4928 * Colors the terminal screen.
4931 void WCMD_color (void) {
4933 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4934 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4936 if (param1[0] != 0x00 && lstrlenW(param1) > 2) {
4937 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4938 return;
4941 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4943 COORD topLeft;
4944 DWORD screenSize;
4945 DWORD color = 0;
4947 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4949 topLeft.X = 0;
4950 topLeft.Y = 0;
4952 /* Convert the color hex digits */
4953 if (param1[0] == 0x00) {
4954 color = defaultColor;
4955 } else {
4956 color = wcstoul(param1, NULL, 16);
4959 /* Fail if fg == bg color */
4960 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4961 errorlevel = 1;
4962 return;
4965 /* Set the current screen contents and ensure all future writes
4966 remain this color */
4967 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4968 SetConsoleTextAttribute(hStdOut, color);
4972 /****************************************************************************
4973 * WCMD_mklink
4976 void WCMD_mklink(WCHAR *args)
4978 int argno = 0;
4979 WCHAR *argN = args;
4980 BOOL isdir = FALSE;
4981 BOOL junction = FALSE;
4982 BOOL hard = FALSE;
4983 BOOL ret = FALSE;
4984 WCHAR file1[MAX_PATH];
4985 WCHAR file2[MAX_PATH];
4987 if (param1[0] == 0x00 || param2[0] == 0x00) {
4988 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4989 return;
4992 file1[0] = 0;
4994 while (argN) {
4995 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4997 if (!argN) break;
4999 WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
5001 if (lstrcmpiW(thisArg, L"/D") == 0)
5002 isdir = TRUE;
5003 else if (lstrcmpiW(thisArg, L"/H") == 0)
5004 hard = TRUE;
5005 else if (lstrcmpiW(thisArg, L"/J") == 0)
5006 junction = TRUE;
5007 else {
5008 if(!file1[0])
5009 lstrcpyW(file1, thisArg);
5010 else
5011 lstrcpyW(file2, thisArg);
5015 if(hard)
5016 ret = CreateHardLinkW(file1, file2, NULL);
5017 else if(!junction)
5018 ret = CreateSymbolicLinkW(file1, file2, isdir);
5019 else
5020 WINE_TRACE("Juction links currently not supported.\n");
5022 if(!ret)
5023 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), file1);