wrc: Store version and characteristics as simple integers.
[wine.git] / programs / cmd / builtins.c
blob963a9eaf3613ccb321a793e93872b94179db2272
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,%ld\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: %ld\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 (%ld)\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 (%ld)\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 (%ld)\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 (%ld)\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=%ld\n",
530 wine_dbgstr_w(dstname), GetLastError());
533 } else {
534 WINE_ERR("Unexpected failure reading from %s, rc=%ld\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 if (!WCMD_get_fullpath(destination->name, ARRAY_SIZE(destname), destname, &filenamepart)) return;
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 if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return;
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 if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return;
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 if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return;
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 if (!WCMD_get_fullpath(filename, ARRAY_SIZE(fpath), fpath, NULL)) return FALSE;
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 if (!WCMD_get_fullpath(argCopy, ARRAY_SIZE(thisDir), thisDir, NULL)) return FALSE;
1324 _wsplitpath(thisDir, drive, dir, fname, ext);
1326 lstrcpyW(thisDir, drive);
1327 lstrcatW(thisDir, dir);
1328 cPos = lstrlenW(thisDir);
1330 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1332 /* Append '*' to the directory */
1333 thisDir[cPos] = '*';
1334 thisDir[cPos+1] = 0x00;
1336 hff = FindFirstFileW(thisDir, &fd);
1338 /* Remove residual '*' */
1339 thisDir[cPos] = 0x00;
1341 if (hff != INVALID_HANDLE_VALUE) {
1342 DIRECTORY_STACK *allDirs = NULL;
1343 DIRECTORY_STACK *lastEntry = NULL;
1345 do {
1346 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1347 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0)) {
1349 DIRECTORY_STACK *nextDir;
1350 WCHAR subParm[MAX_PATH];
1352 /* Work out search parameter in sub dir */
1353 lstrcpyW (subParm, thisDir);
1354 lstrcatW (subParm, fd.cFileName);
1355 lstrcatW (subParm, L"\\");
1356 lstrcatW (subParm, fname);
1357 lstrcatW (subParm, ext);
1358 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1360 /* Allocate memory, add to list */
1361 nextDir = heap_xalloc(sizeof(DIRECTORY_STACK));
1362 if (allDirs == NULL) allDirs = nextDir;
1363 if (lastEntry != NULL) lastEntry->next = nextDir;
1364 lastEntry = nextDir;
1365 nextDir->next = NULL;
1366 nextDir->dirName = heap_strdupW(subParm);
1368 } while (FindNextFileW(hff, &fd) != 0);
1369 FindClose (hff);
1371 /* Go through each subdir doing the delete */
1372 while (allDirs != NULL) {
1373 DIRECTORY_STACK *tempDir;
1375 tempDir = allDirs->next;
1376 found |= WCMD_delete_one (allDirs->dirName);
1378 heap_free(allDirs->dirName);
1379 heap_free(allDirs);
1380 allDirs = tempDir;
1385 return found;
1388 /****************************************************************************
1389 * WCMD_delete
1391 * Delete a file or wildcarded set.
1393 * Note on /A:
1394 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1395 * - Each set is a pattern, eg /ahr /as-r means
1396 * readonly+hidden OR nonreadonly system files
1397 * - The '-' applies to a single field, ie /a:-hr means read only
1398 * non-hidden files
1401 BOOL WCMD_delete (WCHAR *args) {
1402 int argno;
1403 WCHAR *argN;
1404 BOOL argsProcessed = FALSE;
1405 BOOL foundAny = FALSE;
1407 errorlevel = 0;
1409 for (argno=0; ; argno++) {
1410 BOOL found;
1411 WCHAR *thisArg;
1413 argN = NULL;
1414 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1415 if (!argN)
1416 break; /* no more parameters */
1417 if (argN[0] == '/')
1418 continue; /* skip options */
1420 argsProcessed = TRUE;
1421 found = WCMD_delete_one(thisArg);
1422 if (!found)
1423 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1424 foundAny |= found;
1427 /* Handle no valid args */
1428 if (!argsProcessed)
1429 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1431 return foundAny;
1435 * WCMD_strtrim
1437 * Returns a trimmed version of s with all leading and trailing whitespace removed
1438 * Pre: s non NULL
1441 static WCHAR *WCMD_strtrim(const WCHAR *s)
1443 DWORD len = lstrlenW(s);
1444 const WCHAR *start = s;
1445 WCHAR* result;
1447 result = heap_xalloc((len + 1) * sizeof(WCHAR));
1449 while (iswspace(*start)) start++;
1450 if (*start) {
1451 const WCHAR *end = s + len - 1;
1452 while (end > start && iswspace(*end)) end--;
1453 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1454 result[end - start + 1] = '\0';
1455 } else {
1456 result[0] = '\0';
1459 return result;
1462 /****************************************************************************
1463 * WCMD_echo
1465 * Echo input to the screen (or not). We don't try to emulate the bugs
1466 * in DOS (try typing "ECHO ON AGAIN" for an example).
1469 void WCMD_echo (const WCHAR *args)
1471 int count;
1472 const WCHAR *origcommand = args;
1473 WCHAR *trimmed;
1475 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1476 || args[0]==':' || args[0]==';' || args[0]=='/')
1477 args++;
1479 trimmed = WCMD_strtrim(args);
1480 if (!trimmed) return;
1482 count = lstrlenW(trimmed);
1483 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1484 && origcommand[0]!=';' && origcommand[0]!='/') {
1485 if (echo_mode) WCMD_output(WCMD_LoadMessage(WCMD_ECHOPROMPT), L"ON");
1486 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), L"OFF");
1487 heap_free(trimmed);
1488 return;
1491 if (lstrcmpiW(trimmed, L"ON") == 0)
1492 echo_mode = TRUE;
1493 else if (lstrcmpiW(trimmed, L"OFF") == 0)
1494 echo_mode = FALSE;
1495 else {
1496 WCMD_output_asis (args);
1497 WCMD_output_asis(L"\r\n");
1499 heap_free(trimmed);
1502 /*****************************************************************************
1503 * WCMD_part_execute
1505 * Execute a command, and any && or bracketed follow on to the command. The
1506 * first command to be executed may not be at the front of the
1507 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1509 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1510 BOOL isIF, BOOL executecmds)
1512 CMD_LIST *curPosition = *cmdList;
1513 int myDepth = (*cmdList)->bracketDepth;
1515 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d), isIF(%d)\n", cmdList,
1516 wine_dbgstr_w(firstcmd), executecmds, isIF);
1518 /* Skip leading whitespace between condition and the command */
1519 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1521 /* Process the first command, if there is one */
1522 if (executecmds && firstcmd && *firstcmd) {
1523 WCHAR *command = heap_strdupW(firstcmd);
1524 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1525 heap_free(command);
1529 /* If it didn't move the position, step to next command */
1530 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1532 /* Process any other parts of the command */
1533 if (*cmdList) {
1534 BOOL processThese = executecmds;
1536 while (*cmdList) {
1537 /* execute all appropriate commands */
1538 curPosition = *cmdList;
1540 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d) processThese(%d)\n",
1541 *cmdList,
1542 (*cmdList)->prevDelim,
1543 (*cmdList)->bracketDepth,
1544 myDepth,
1545 processThese);
1547 /* Execute any statements appended to the line */
1548 /* FIXME: Only if previous call worked for && or failed for || */
1549 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1550 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1551 if (processThese && (*cmdList)->command) {
1552 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1553 cmdList, FALSE);
1555 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1557 /* Execute any appended to the statement with (...) */
1558 } else if ((*cmdList)->bracketDepth > myDepth) {
1559 if (processThese) {
1560 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1561 } else {
1562 WINE_TRACE("Skipping command %p due to stack depth\n", *cmdList);
1564 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1566 /* End of the command - does 'ELSE ' follow as the next command? */
1567 } else {
1568 if (isIF && WCMD_keyword_ws_found(L"else", (*cmdList)->command)) {
1569 /* Swap between if and else processing */
1570 processThese = !executecmds;
1572 /* Process the ELSE part */
1573 if (processThese) {
1574 const int keyw_len = lstrlenW(L"else") + 1;
1575 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1577 /* Skip leading whitespace between condition and the command */
1578 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1579 if (*cmd) {
1580 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1582 } else {
1583 /* Loop skipping all commands until we get back to the current
1584 depth, including skipping commands and their subsequent
1585 pipes (eg cmd | prog) */
1586 do {
1587 *cmdList = (*cmdList)->nextcommand;
1588 } while (*cmdList &&
1589 ((*cmdList)->bracketDepth > myDepth ||
1590 (*cmdList)->prevDelim));
1592 /* After the else is complete, we need to now process subsequent commands */
1593 processThese = TRUE;
1595 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1597 /* If we were in an IF statement and we didn't find an else and yet we get back to
1598 the same bracket depth as the IF, then the IF statement is over. This is required
1599 to handle nested ifs properly */
1600 } else if (isIF && (*cmdList)->bracketDepth == myDepth) {
1601 if (WCMD_keyword_ws_found(L"do", (*cmdList)->command)) {
1602 WINE_TRACE("Still inside FOR-loop, not an end of IF statement\n");
1603 *cmdList = (*cmdList)->nextcommand;
1604 } else {
1605 WINE_TRACE("Found end of this nested IF statement, ending this if\n");
1606 break;
1608 } else if (!processThese) {
1609 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1610 WINE_TRACE("Skipping this command, as in not process mode (next = %p)\n", *cmdList);
1611 } else {
1612 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1613 break;
1618 return;
1621 /*****************************************************************************
1622 * WCMD_parse_forf_options
1624 * Parses the for /f 'options', extracting the values and validating the
1625 * keywords. Note all keywords are optional.
1626 * Parameters:
1627 * options [I] The unparsed parameter string
1628 * eol [O] Set to the comment character (eol=x)
1629 * skip [O] Set to the number of lines to skip (skip=xx)
1630 * delims [O] Set to the token delimiters (delims=)
1631 * tokens [O] Set to the requested tokens, as provided (tokens=)
1632 * usebackq [O] Set to TRUE if usebackq found
1634 * Returns TRUE on success, FALSE on syntax error
1637 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1638 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1641 WCHAR *pos = options;
1642 int len = lstrlenW(pos);
1643 const int eol_len = lstrlenW(L"eol=");
1644 const int skip_len = lstrlenW(L"skip=");
1645 const int tokens_len = lstrlenW(L"tokens=");
1646 const int delims_len = lstrlenW(L"delims=");
1647 const int usebackq_len = lstrlenW(L"usebackq");
1649 /* Initialize to defaults */
1650 lstrcpyW(delims, L" \t");
1651 lstrcpyW(tokens, L"1");
1652 *eol = 0;
1653 *skip = 0;
1654 *usebackq = FALSE;
1656 /* Strip (optional) leading and trailing quotes */
1657 if ((*pos == '"') && (pos[len-1] == '"')) {
1658 pos[len-1] = 0;
1659 pos++;
1662 /* Process each keyword */
1663 while (pos && *pos) {
1664 if (*pos == ' ' || *pos == '\t') {
1665 pos++;
1667 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1668 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1669 pos, eol_len, L"eol=", eol_len) == CSTR_EQUAL) {
1670 *eol = *(pos + eol_len);
1671 pos = pos + eol_len + 1;
1672 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1674 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1675 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1676 pos, skip_len, L"skip=", skip_len) == CSTR_EQUAL) {
1677 WCHAR *nextchar = NULL;
1678 pos = pos + skip_len;
1679 *skip = wcstoul(pos, &nextchar, 0);
1680 WINE_TRACE("Found skip as %d lines\n", *skip);
1681 pos = nextchar;
1683 /* Save if usebackq semantics are in effect */
1684 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, pos,
1685 usebackq_len, L"usebackq", usebackq_len) == CSTR_EQUAL) {
1686 *usebackq = TRUE;
1687 pos = pos + usebackq_len;
1688 WINE_TRACE("Found usebackq\n");
1690 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1691 if you finish the optionsroot string with delims= otherwise the space is
1692 just a token delimiter! */
1693 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1694 pos, delims_len, L"delims=", delims_len) == CSTR_EQUAL) {
1695 int i=0;
1697 pos = pos + delims_len;
1698 while (*pos && *pos != ' ') {
1699 delims[i++] = *pos;
1700 pos++;
1702 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1703 delims[i++] = 0; /* Null terminate the delims */
1704 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1706 /* Save the tokens being requested */
1707 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1708 pos, tokens_len, L"tokens=", tokens_len) == CSTR_EQUAL) {
1709 int i=0;
1711 pos = pos + tokens_len;
1712 while (*pos && *pos != ' ') {
1713 tokens[i++] = *pos;
1714 pos++;
1716 tokens[i++] = 0; /* Null terminate the tokens */
1717 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1719 } else {
1720 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1721 return FALSE;
1724 return TRUE;
1727 /*****************************************************************************
1728 * WCMD_add_dirstowalk
1730 * When recursing through directories (for /r), we need to add to the list of
1731 * directories still to walk, any subdirectories of the one we are processing.
1733 * Parameters
1734 * options [I] The remaining list of directories still to process
1736 * Note this routine inserts the subdirectories found between the entry being
1737 * processed, and any other directory still to be processed, mimicking what
1738 * Windows does
1740 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1741 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1742 WCHAR fullitem[MAX_PATH];
1743 WIN32_FIND_DATAW fd;
1744 HANDLE hff;
1746 /* Build a generic search and add all directories on the list of directories
1747 still to walk */
1748 lstrcpyW(fullitem, dirsToWalk->dirName);
1749 lstrcatW(fullitem, L"\\*");
1750 hff = FindFirstFileW(fullitem, &fd);
1751 if (hff != INVALID_HANDLE_VALUE) {
1752 do {
1753 WINE_TRACE("Looking for subdirectories\n");
1754 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1755 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
1757 /* Allocate memory, add to list */
1758 DIRECTORY_STACK *toWalk = heap_xalloc(sizeof(DIRECTORY_STACK));
1759 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1760 toWalk->next = remainingDirs->next;
1761 remainingDirs->next = toWalk;
1762 remainingDirs = toWalk;
1763 toWalk->dirName = heap_xalloc(sizeof(WCHAR) * (lstrlenW(dirsToWalk->dirName) + 2 + lstrlenW(fd.cFileName)));
1764 lstrcpyW(toWalk->dirName, dirsToWalk->dirName);
1765 lstrcatW(toWalk->dirName, L"\\");
1766 lstrcatW(toWalk->dirName, fd.cFileName);
1767 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1768 toWalk, toWalk->next);
1770 } while (FindNextFileW(hff, &fd) != 0);
1771 WINE_TRACE("Finished adding all subdirectories\n");
1772 FindClose (hff);
1776 /**************************************************************************
1777 * WCMD_for_nexttoken
1779 * Parse the token= line, identifying the next highest number not processed
1780 * so far. Count how many tokens are referred (including duplicates) and
1781 * optionally return that, plus optionally indicate if the tokens= line
1782 * ends in a star.
1784 * Parameters:
1785 * lasttoken [I] - Identifies the token index of the last one
1786 * returned so far (-1 used for first loop)
1787 * tokenstr [I] - The specified tokens= line
1788 * firstCmd [O] - Optionally indicate how many tokens are listed
1789 * doAll [O] - Optionally indicate if line ends with *
1790 * duplicates [O] - Optionally indicate if there is any evidence of
1791 * overlaying tokens in the string
1792 * Note the caller should keep a running track of duplicates as the tokens
1793 * are recursively passed. If any have duplicates, then the * token should
1794 * not be honoured.
1796 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1797 int *totalfound, BOOL *doall,
1798 BOOL *duplicates)
1800 WCHAR *pos = tokenstr;
1801 int nexttoken = -1;
1803 if (totalfound) *totalfound = 0;
1804 if (doall) *doall = FALSE;
1805 if (duplicates) *duplicates = FALSE;
1807 WINE_TRACE("Find next token after %d in %s\n", lasttoken,
1808 wine_dbgstr_w(tokenstr));
1810 /* Loop through the token string, parsing it. Valid syntax is:
1811 token=m or x-y with comma delimiter and optionally * to finish*/
1812 while (*pos) {
1813 int nextnumber1, nextnumber2 = -1;
1814 WCHAR *nextchar;
1816 /* Remember if the next character is a star, it indicates a need to
1817 show all remaining tokens and should be the last character */
1818 if (*pos == '*') {
1819 if (doall) *doall = TRUE;
1820 if (totalfound) (*totalfound)++;
1821 /* If we have not found a next token to return, then indicate
1822 time to process the star */
1823 if (nexttoken == -1) {
1824 if (lasttoken == -1) {
1825 /* Special case the syntax of tokens=* which just means get whole line */
1826 nexttoken = 0;
1827 } else {
1828 nexttoken = lasttoken;
1831 break;
1834 /* Get the next number */
1835 nextnumber1 = wcstoul(pos, &nextchar, 10);
1837 /* If it is followed by a minus, it's a range, so get the next one as well */
1838 if (*nextchar == '-') {
1839 nextnumber2 = wcstoul(nextchar+1, &nextchar, 10);
1841 /* We want to return the lowest number that is higher than lasttoken
1842 but only if range is positive */
1843 if (nextnumber2 >= nextnumber1 &&
1844 lasttoken < nextnumber2) {
1846 int nextvalue;
1847 if (nexttoken == -1) {
1848 nextvalue = max(nextnumber1, (lasttoken+1));
1849 } else {
1850 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1853 /* Flag if duplicates identified */
1854 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1856 nexttoken = nextvalue;
1859 /* Update the running total for the whole range */
1860 if (nextnumber2 >= nextnumber1 && totalfound) {
1861 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1863 pos = nextchar;
1865 } else if (pos != nextchar) {
1866 if (totalfound) (*totalfound)++;
1868 /* See if the number found is one we have already seen */
1869 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1871 /* We want to return the lowest number that is higher than lasttoken */
1872 if (lasttoken < nextnumber1 &&
1873 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1874 nexttoken = nextnumber1;
1876 pos = nextchar;
1878 } else {
1879 /* Step on to the next character, usually over comma */
1880 if (*pos) pos++;
1885 /* Return result */
1886 if (nexttoken == -1) {
1887 WINE_TRACE("No next token found, previous was %d\n", lasttoken);
1888 nexttoken = lasttoken;
1889 } else if (nexttoken==lasttoken && doall && *doall) {
1890 WINE_TRACE("Request for all remaining tokens now\n");
1891 } else {
1892 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1894 if (totalfound) WINE_TRACE("Found total tokens to be %d\n", *totalfound);
1895 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1896 return nexttoken;
1899 /**************************************************************************
1900 * WCMD_parse_line
1902 * When parsing file or string contents (for /f), once the string to parse
1903 * has been identified, handle the various options and call the do part
1904 * if appropriate.
1906 * Parameters:
1907 * cmdStart [I] - Identifies the list of commands making up the
1908 * for loop body (especially if brackets in use)
1909 * firstCmd [I] - The textual start of the command after the DO
1910 * which is within the first item of cmdStart
1911 * cmdEnd [O] - Identifies where to continue after the DO
1912 * variable [I] - The variable identified on the for line
1913 * buffer [I] - The string to parse
1914 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1915 * forf_skip [I/O] - How many lines to skip first
1916 * forf_eol [I] - The 'end of line' (comment) character
1917 * forf_delims [I] - The delimiters to use when breaking the string apart
1918 * forf_tokens [I] - The tokens to use when breaking the string apart
1920 static void WCMD_parse_line(CMD_LIST *cmdStart,
1921 const WCHAR *firstCmd,
1922 CMD_LIST **cmdEnd,
1923 const WCHAR variable,
1924 WCHAR *buffer,
1925 BOOL *doExecuted,
1926 int *forf_skip,
1927 WCHAR forf_eol,
1928 WCHAR *forf_delims,
1929 WCHAR *forf_tokens) {
1931 WCHAR *parm;
1932 FOR_CONTEXT oldcontext;
1933 int varidx, varoffset;
1934 int nexttoken, lasttoken = -1;
1935 BOOL starfound = FALSE;
1936 BOOL thisduplicate = FALSE;
1937 BOOL anyduplicates = FALSE;
1938 int totalfound;
1939 static WCHAR emptyW[] = L"";
1941 /* Skip lines if requested */
1942 if (*forf_skip) {
1943 (*forf_skip)--;
1944 return;
1947 /* Save away any existing for variable context (e.g. nested for loops) */
1948 oldcontext = forloopcontext;
1950 /* Extract the parameters based on the tokens= value (There will always
1951 be some value, as if it is not supplied, it defaults to tokens=1).
1952 Rough logic:
1953 Count how many tokens are named in the line, identify the lowest
1954 Empty (set to null terminated string) that number of named variables
1955 While lasttoken != nextlowest
1956 %letter = parameter number 'nextlowest'
1957 letter++ (if >26 or >52 abort)
1958 Go through token= string finding next lowest number
1959 If token ends in * set %letter = raw position of token(nextnumber+1)
1961 lasttoken = -1;
1962 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1963 &starfound, &thisduplicate);
1964 varidx = FOR_VAR_IDX(variable);
1966 /* Empty out variables */
1967 for (varoffset=0;
1968 varidx >= 0 && varoffset<totalfound && (((varidx%26) + varoffset) < 26);
1969 varoffset++) {
1970 forloopcontext.variable[varidx + varoffset] = emptyW;
1973 /* Loop extracting the tokens
1974 Note: nexttoken of 0 means there were no tokens requested, to handle
1975 the special case of tokens=* */
1976 varoffset = 0;
1977 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1978 while (varidx >= 0 && (nexttoken > 0 && (nexttoken > lasttoken))) {
1979 anyduplicates |= thisduplicate;
1981 /* Extract the token number requested and set into the next variable context */
1982 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims);
1983 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1984 varidx + varoffset, wine_dbgstr_w(parm));
1985 if (varidx >=0) {
1986 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1987 varoffset++;
1988 if (((varidx%26)+varoffset) >= 26) break;
1991 /* Find the next token */
1992 lasttoken = nexttoken;
1993 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1994 &starfound, &thisduplicate);
1997 /* If all the rest of the tokens were requested, and there is still space in
1998 the variable range, write them now */
1999 if (!anyduplicates && starfound && varidx >= 0 && (((varidx%26) + varoffset) < 26)) {
2000 nexttoken++;
2001 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
2002 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2003 varidx + varoffset, wine_dbgstr_w(parm));
2004 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2007 /* Execute the body of the foor loop with these values */
2008 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
2009 CMD_LIST *thisCmdStart = cmdStart;
2010 *doExecuted = TRUE;
2011 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
2012 *cmdEnd = thisCmdStart;
2015 /* Free the duplicated strings, and restore the context */
2016 if (varidx >=0) {
2017 int i;
2018 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
2019 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
2020 (forloopcontext.variable[i] != emptyW)) {
2021 heap_free(forloopcontext.variable[i]);
2026 /* Restore the original for variable contextx */
2027 forloopcontext = oldcontext;
2030 /**************************************************************************
2031 * WCMD_forf_getinputhandle
2033 * Return a file handle which can be used for reading the input lines,
2034 * either to a specific file (which may be quote delimited as we have to
2035 * read the parameters in raw mode) or to a command which we need to
2036 * execute. The command being executed runs in its own shell and stores
2037 * its data in a temporary file.
2039 * Parameters:
2040 * usebackq [I] - Indicates whether usebackq is in effect or not
2041 * itemStr [I] - The item to be handled, either a filename or
2042 * whole command string to execute
2043 * iscmd [I] - Identifies whether this is a command or not
2045 * Returns a file handle which can be used to read the input lines from.
2047 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2048 WCHAR temp_str[MAX_PATH];
2049 WCHAR temp_file[MAX_PATH];
2050 WCHAR temp_cmd[MAXSTRING];
2051 WCHAR *trimmed = NULL;
2052 HANDLE hinput = INVALID_HANDLE_VALUE;
2054 /* Remove leading and trailing character (but there may be trailing whitespace too) */
2055 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2056 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2057 (!iscmd && (itemstr[0] == '"' && usebackq)))
2059 trimmed = WCMD_strtrim(itemstr);
2060 if (trimmed) {
2061 itemstr = trimmed;
2063 itemstr[lstrlenW(itemstr)-1] = 0x00;
2064 itemstr++;
2067 if (iscmd) {
2068 /* Get temp filename */
2069 GetTempPathW(ARRAY_SIZE(temp_str), temp_str);
2070 GetTempFileNameW(temp_str, L"CMD", 0, temp_file);
2072 /* Redirect output to the temporary file */
2073 wsprintfW(temp_str, L">%s", temp_file);
2074 wsprintfW(temp_cmd, L"CMD.EXE /C %s", itemstr);
2075 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2076 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2077 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2079 /* Open the file, read line by line and process */
2080 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2081 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2083 } else {
2084 /* Open the file, read line by line and process */
2085 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2086 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2087 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2089 heap_free(trimmed);
2090 return hinput;
2093 /**************************************************************************
2094 * WCMD_for
2096 * Batch file loop processing.
2098 * On entry: cmdList contains the syntax up to the set
2099 * next cmdList and all in that bracket contain the set data
2100 * next cmdlist contains the DO cmd
2101 * following that is either brackets or && entries (as per if)
2105 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2107 WIN32_FIND_DATAW fd;
2108 HANDLE hff;
2109 int i;
2110 const int in_len = lstrlenW(L"in");
2111 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2112 WCHAR variable[4];
2113 int varidx = -1;
2114 WCHAR *oldvariablevalue;
2115 WCHAR *firstCmd;
2116 int thisDepth;
2117 WCHAR optionsRoot[MAX_PATH];
2118 DIRECTORY_STACK *dirsToWalk = NULL;
2119 BOOL expandDirs = FALSE;
2120 BOOL useNumbers = FALSE;
2121 BOOL doFileset = FALSE;
2122 BOOL doRecurse = FALSE;
2123 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2124 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2125 int itemNum;
2126 CMD_LIST *thisCmdStart;
2127 int parameterNo = 0;
2128 WCHAR forf_eol = 0;
2129 int forf_skip = 0;
2130 WCHAR forf_delims[256];
2131 WCHAR forf_tokens[MAXSTRING];
2132 BOOL forf_usebackq = FALSE;
2134 /* Handle optional qualifiers (multiple are allowed) */
2135 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2137 optionsRoot[0] = 0;
2138 while (thisArg && *thisArg == '/') {
2139 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2140 thisArg++;
2141 switch (towupper(*thisArg)) {
2142 case 'D': expandDirs = TRUE; break;
2143 case 'L': useNumbers = TRUE; break;
2145 /* Recursive is special case - /R can have an optional path following it */
2146 /* filenamesets are another special case - /F can have an optional options following it */
2147 case 'R':
2148 case 'F':
2150 /* When recursing directories, use current directory as the starting point unless
2151 subsequently overridden */
2152 doRecurse = (towupper(*thisArg) == 'R');
2153 if (doRecurse) GetCurrentDirectoryW(ARRAY_SIZE(optionsRoot), optionsRoot);
2155 doFileset = (towupper(*thisArg) == 'F');
2157 /* Retrieve next parameter to see if is root/options (raw form required
2158 with for /f, or unquoted in for /r) */
2159 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2161 /* Next parm is either qualifier, path/options or variable -
2162 only care about it if it is the path/options */
2163 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2164 parameterNo++;
2165 lstrcpyW(optionsRoot, thisArg);
2167 break;
2169 default:
2170 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2173 /* Step to next token */
2174 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2177 /* Ensure line continues with variable */
2178 if (*thisArg != '%') {
2179 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2180 return;
2183 /* With for /f parse the options if provided */
2184 if (doFileset) {
2185 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2186 forf_delims, forf_tokens, &forf_usebackq))
2188 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2189 return;
2192 /* Set up the list of directories to recurse if we are going to */
2193 } else if (doRecurse) {
2194 /* Allocate memory, add to list */
2195 dirsToWalk = heap_xalloc(sizeof(DIRECTORY_STACK));
2196 dirsToWalk->next = NULL;
2197 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2198 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2201 /* Variable should follow */
2202 lstrcpyW(variable, thisArg);
2203 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2204 varidx = FOR_VAR_IDX(variable[1]);
2206 /* Ensure line continues with IN */
2207 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2208 if (!thisArg
2209 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2210 thisArg, in_len, L"in", in_len) == CSTR_EQUAL)) {
2211 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2212 return;
2215 /* Save away where the set of data starts and the variable */
2216 thisDepth = (*cmdList)->bracketDepth;
2217 *cmdList = (*cmdList)->nextcommand;
2218 setStart = (*cmdList);
2220 /* Skip until the close bracket */
2221 WINE_TRACE("Searching %p as the set\n", *cmdList);
2222 while (*cmdList &&
2223 (*cmdList)->command != NULL &&
2224 (*cmdList)->bracketDepth > thisDepth) {
2225 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2226 *cmdList = (*cmdList)->nextcommand;
2229 /* Skip the close bracket, if there is one */
2230 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2232 /* Syntax error if missing close bracket, or nothing following it
2233 and once we have the complete set, we expect a DO */
2234 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2235 if ((*cmdList == NULL) || !WCMD_keyword_ws_found(L"do", (*cmdList)->command)) {
2236 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2237 return;
2240 cmdEnd = *cmdList;
2242 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2243 mode, or once for the rest of the time. */
2244 do {
2246 /* Save away the starting position for the commands (and offset for the
2247 first one) */
2248 cmdStart = *cmdList;
2249 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2250 itemNum = 0;
2252 /* If we are recursing directories (ie /R), add all sub directories now, then
2253 prefix the root when searching for the item */
2254 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2256 thisSet = setStart;
2257 /* Loop through all set entries */
2258 while (thisSet &&
2259 thisSet->command != NULL &&
2260 thisSet->bracketDepth >= thisDepth) {
2262 /* Loop through all entries on the same line */
2263 WCHAR *staticitem;
2264 WCHAR *itemStart;
2265 WCHAR buffer[MAXSTRING];
2267 WINE_TRACE("Processing for set %p\n", thisSet);
2268 i = 0;
2269 while (*(staticitem = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2272 * If the parameter within the set has a wildcard then search for matching files
2273 * otherwise do a literal substitution.
2276 /* Take a copy of the item returned from WCMD_parameter as it is held in a
2277 static buffer which can be overwritten during parsing of the for body */
2278 WCHAR item[MAXSTRING];
2279 lstrcpyW(item, staticitem);
2281 thisCmdStart = cmdStart;
2283 itemNum++;
2284 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2286 if (!useNumbers && !doFileset) {
2287 WCHAR fullitem[MAX_PATH];
2288 int prefixlen = 0;
2290 /* Now build the item to use / search for in the specified directory,
2291 as it is fully qualified in the /R case */
2292 if (dirsToWalk) {
2293 lstrcpyW(fullitem, dirsToWalk->dirName);
2294 lstrcatW(fullitem, L"\\");
2295 lstrcatW(fullitem, item);
2296 } else {
2297 WCHAR *prefix = wcsrchr(item, '\\');
2298 if (prefix) prefixlen = (prefix - item) + 1;
2299 lstrcpyW(fullitem, item);
2302 if (wcspbrk(fullitem, L"*?")) {
2303 hff = FindFirstFileW(fullitem, &fd);
2304 if (hff != INVALID_HANDLE_VALUE) {
2305 do {
2306 BOOL isDirectory = FALSE;
2308 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2310 /* Handle as files or dirs appropriately, but ignore . and .. */
2311 if (isDirectory == expandDirs &&
2312 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
2314 thisCmdStart = cmdStart;
2315 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2317 if (doRecurse) {
2318 lstrcpyW(fullitem, dirsToWalk->dirName);
2319 lstrcatW(fullitem, L"\\");
2320 lstrcatW(fullitem, fd.cFileName);
2321 } else {
2322 if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
2323 fullitem[prefixlen] = 0x00;
2324 lstrcatW(fullitem, fd.cFileName);
2326 doExecuted = TRUE;
2328 /* Save away any existing for variable context (e.g. nested for loops)
2329 and restore it after executing the body of this for loop */
2330 if (varidx >= 0) {
2331 oldvariablevalue = forloopcontext.variable[varidx];
2332 forloopcontext.variable[varidx] = fullitem;
2334 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2335 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2337 cmdEnd = thisCmdStart;
2339 } while (FindNextFileW(hff, &fd) != 0);
2340 FindClose (hff);
2342 } else {
2343 doExecuted = TRUE;
2345 /* Save away any existing for variable context (e.g. nested for loops)
2346 and restore it after executing the body of this for loop */
2347 if (varidx >= 0) {
2348 oldvariablevalue = forloopcontext.variable[varidx];
2349 forloopcontext.variable[varidx] = fullitem;
2351 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2352 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2354 cmdEnd = thisCmdStart;
2357 } else if (useNumbers) {
2358 /* Convert the first 3 numbers to signed longs and save */
2359 if (itemNum <=3) numbers[itemNum-1] = wcstol(item, NULL, 10);
2360 /* else ignore them! */
2362 /* Filesets - either a list of files, or a command to run and parse the output */
2363 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2364 (forf_usebackq && *itemStart != '\''))) {
2366 HANDLE input;
2367 WCHAR *itemparm;
2369 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2370 wine_dbgstr_w(item));
2372 /* If backquote or single quote, we need to launch that command
2373 and parse the results - use a temporary file */
2374 if ((forf_usebackq && *itemStart == '`') ||
2375 (!forf_usebackq && *itemStart == '\'')) {
2377 /* Use itemstart because the command is the whole set, not just the first token */
2378 itemparm = itemStart;
2379 } else {
2381 /* Use item because the file to process is just the first item in the set */
2382 itemparm = item;
2384 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2386 /* Process the input file */
2387 if (input == INVALID_HANDLE_VALUE) {
2388 WCMD_print_error ();
2389 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2390 errorlevel = 1;
2391 return; /* FOR loop aborts at first failure here */
2393 } else {
2395 /* Read line by line until end of file */
2396 while (WCMD_fgets(buffer, ARRAY_SIZE(buffer), input)) {
2397 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2398 &forf_skip, forf_eol, forf_delims, forf_tokens);
2399 buffer[0] = 0;
2401 CloseHandle (input);
2404 /* When we have processed the item as a whole command, abort future set processing */
2405 if (itemparm==itemStart) {
2406 thisSet = NULL;
2407 break;
2410 /* Filesets - A string literal */
2411 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2412 (forf_usebackq && *itemStart == '\''))) {
2414 /* Remove leading and trailing character, ready to parse with delims= delimiters
2415 Note that the last quote is removed from the set and the string terminates
2416 there to mimic windows */
2417 WCHAR *strend = wcsrchr(itemStart, forf_usebackq?'\'':'"');
2418 if (strend) {
2419 *strend = 0x00;
2420 itemStart++;
2423 /* Copy the item away from the global buffer used by WCMD_parameter */
2424 lstrcpyW(buffer, itemStart);
2425 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2426 &forf_skip, forf_eol, forf_delims, forf_tokens);
2428 /* Only one string can be supplied in the whole set, abort future set processing */
2429 thisSet = NULL;
2430 break;
2433 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2434 i++;
2437 /* Move onto the next set line */
2438 if (thisSet) thisSet = thisSet->nextcommand;
2441 /* If /L is provided, now run the for loop */
2442 if (useNumbers) {
2443 WCHAR thisNum[20];
2445 WINE_TRACE("FOR /L provided range from %ld to %ld step %ld\n",
2446 numbers[0], numbers[2], numbers[1]);
2447 for (i=numbers[0];
2448 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2449 i=i + numbers[1]) {
2451 swprintf(thisNum, ARRAY_SIZE(thisNum), L"%d", i);
2452 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2454 thisCmdStart = cmdStart;
2455 doExecuted = TRUE;
2457 /* Save away any existing for variable context (e.g. nested for loops)
2458 and restore it after executing the body of this for loop */
2459 if (varidx >= 0) {
2460 oldvariablevalue = forloopcontext.variable[varidx];
2461 forloopcontext.variable[varidx] = thisNum;
2463 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2464 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2466 cmdEnd = thisCmdStart;
2469 /* If we are walking directories, move on to any which remain */
2470 if (dirsToWalk != NULL) {
2471 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2472 heap_free(dirsToWalk->dirName);
2473 heap_free(dirsToWalk);
2474 dirsToWalk = nextDir;
2475 if (dirsToWalk) WINE_TRACE("Moving to next directory to iterate: %s\n",
2476 wine_dbgstr_w(dirsToWalk->dirName));
2477 else WINE_TRACE("Finished all directories.\n");
2480 } while (dirsToWalk != NULL);
2482 /* Now skip over the do part if we did not perform the for loop so far.
2483 We store in cmdEnd the next command after the do block, but we only
2484 know this if something was run. If it has not been, we need to calculate
2485 it. */
2486 if (!doExecuted) {
2487 thisCmdStart = cmdStart;
2488 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2489 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2490 cmdEnd = thisCmdStart;
2493 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2494 all processing, OR it should be pointing to the end of && processing OR
2495 it should be pointing at the NULL end of bracket for the DO. The return
2496 value needs to be the NEXT command to execute, which it either is, or
2497 we need to step over the closing bracket */
2498 *cmdList = cmdEnd;
2499 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2502 /**************************************************************************
2503 * WCMD_give_help
2505 * Simple on-line help. Help text is stored in the resource file.
2508 void WCMD_give_help (const WCHAR *args)
2510 size_t i;
2512 args = WCMD_skip_leading_spaces((WCHAR*) args);
2513 if (!*args) {
2514 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2516 else {
2517 /* Display help message for builtin commands */
2518 for (i=0; i<=WCMD_EXIT; i++) {
2519 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2520 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2521 WCMD_output_asis (WCMD_LoadMessage(i));
2522 return;
2525 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2526 for (i = 0; i <= ARRAY_SIZE(externals); i++) {
2527 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2528 args, -1, externals[i], -1) == CSTR_EQUAL) {
2529 WCHAR cmd[128];
2530 lstrcpyW(cmd, args);
2531 lstrcatW(cmd, L" /?");
2532 WCMD_run_program(cmd, FALSE);
2533 return;
2536 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2538 return;
2541 /****************************************************************************
2542 * WCMD_go_to
2544 * Batch file jump instruction. Not the most efficient algorithm ;-)
2545 * Prints error message if the specified label cannot be found - the file pointer is
2546 * then at EOF, effectively stopping the batch file.
2547 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2550 void WCMD_goto (CMD_LIST **cmdList) {
2552 WCHAR string[MAX_PATH];
2553 WCHAR *labelend = NULL;
2554 const WCHAR labelEndsW[] = L"><|& :\t";
2556 /* Do not process any more parts of a processed multipart or multilines command */
2557 if (cmdList) *cmdList = NULL;
2559 if (context != NULL) {
2560 WCHAR *paramStart = param1, *str;
2562 if (param1[0] == 0x00) {
2563 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2564 return;
2567 /* Handle special :EOF label */
2568 if (lstrcmpiW(L":eof", param1) == 0) {
2569 context -> skip_rest = TRUE;
2570 return;
2573 /* Support goto :label as well as goto label plus remove trailing chars */
2574 if (*paramStart == ':') paramStart++;
2575 labelend = wcspbrk(paramStart, labelEndsW);
2576 if (labelend) *labelend = 0x00;
2577 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
2579 /* Loop through potentially twice - once from current file position
2580 through to the end, and second time from start to current file
2581 position */
2582 if (*paramStart) {
2583 int loop;
2584 LARGE_INTEGER startli;
2585 for (loop=0; loop<2; loop++) {
2586 if (loop==0) {
2587 /* On first loop, save the file size */
2588 startli.QuadPart = 0;
2589 startli.u.LowPart = SetFilePointer(context -> h, startli.u.LowPart,
2590 &startli.u.HighPart, FILE_CURRENT);
2591 } else {
2592 /* On second loop, start at the beginning of the file */
2593 WINE_TRACE("Label not found, trying from beginning of file\n");
2594 if (loop==1) SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2597 while (WCMD_fgets (string, ARRAY_SIZE(string), context -> h)) {
2598 str = string;
2600 /* Ignore leading whitespace or no-echo character */
2601 while (*str=='@' || iswspace (*str)) str++;
2603 /* If the first real character is a : then this is a label */
2604 if (*str == ':') {
2605 str++;
2607 /* Skip spaces between : and label */
2608 while (iswspace (*str)) str++;
2609 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
2611 /* Label ends at whitespace or redirection characters */
2612 labelend = wcspbrk(str, labelEndsW);
2613 if (labelend) *labelend = 0x00;
2614 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
2616 if (lstrcmpiW (str, paramStart) == 0) return;
2619 /* See if we have gone beyond the end point if second time through */
2620 if (loop==1) {
2621 LARGE_INTEGER curli;
2622 curli.QuadPart = 0;
2623 curli.u.LowPart = SetFilePointer(context -> h, curli.u.LowPart,
2624 &curli.u.HighPart, FILE_CURRENT);
2625 if (curli.QuadPart > startli.QuadPart) {
2626 WINE_TRACE("Reached wrap point, label not found\n");
2627 break;
2634 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2635 context -> skip_rest = TRUE;
2637 return;
2640 /*****************************************************************************
2641 * WCMD_pushd
2643 * Push a directory onto the stack
2646 void WCMD_pushd (const WCHAR *args)
2648 struct env_stack *curdir;
2649 WCHAR *thisdir;
2651 if (wcschr(args, '/') != NULL) {
2652 SetLastError(ERROR_INVALID_PARAMETER);
2653 WCMD_print_error();
2654 return;
2657 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2658 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2659 if( !curdir || !thisdir ) {
2660 LocalFree(curdir);
2661 LocalFree(thisdir);
2662 WINE_ERR ("out of memory\n");
2663 return;
2666 /* Change directory using CD code with /D parameter */
2667 lstrcpyW(quals, L"/D");
2668 GetCurrentDirectoryW (1024, thisdir);
2669 errorlevel = 0;
2670 WCMD_setshow_default(args);
2671 if (errorlevel) {
2672 LocalFree(curdir);
2673 LocalFree(thisdir);
2674 return;
2675 } else {
2676 curdir -> next = pushd_directories;
2677 curdir -> strings = thisdir;
2678 if (pushd_directories == NULL) {
2679 curdir -> u.stackdepth = 1;
2680 } else {
2681 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2683 pushd_directories = curdir;
2688 /*****************************************************************************
2689 * WCMD_popd
2691 * Pop a directory from the stack
2694 void WCMD_popd (void) {
2695 struct env_stack *temp = pushd_directories;
2697 if (!pushd_directories)
2698 return;
2700 /* pop the old environment from the stack, and make it the current dir */
2701 pushd_directories = temp->next;
2702 SetCurrentDirectoryW(temp->strings);
2703 LocalFree (temp->strings);
2704 LocalFree (temp);
2707 /*******************************************************************
2708 * evaluate_if_comparison
2710 * Evaluates an "if" comparison operation
2712 * PARAMS
2713 * leftOperand [I] left operand, non NULL
2714 * operator [I] "if" binary comparison operator, non NULL
2715 * rightOperand [I] right operand, non NULL
2716 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2718 * RETURNS
2719 * Success: 1 if operator applied to the operands evaluates to TRUE
2720 * 0 if operator applied to the operands evaluates to FALSE
2721 * Failure: -1 if operator is not recognized
2723 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2724 const WCHAR *rightOperand, int caseInsensitive)
2726 WCHAR *endptr_leftOp, *endptr_rightOp;
2727 long int leftOperand_int, rightOperand_int;
2728 BOOL int_operands;
2730 /* == is a special case, as it always compares strings */
2731 if (!lstrcmpiW(operator, L"=="))
2732 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2733 : lstrcmpW (leftOperand, rightOperand) == 0;
2735 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2736 leftOperand_int = wcstol(leftOperand, &endptr_leftOp, 0);
2737 rightOperand_int = wcstol(rightOperand, &endptr_rightOp, 0);
2738 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2740 /* Perform actual (integer or string) comparison */
2741 if (!lstrcmpiW(operator, L"lss")) {
2742 if (int_operands)
2743 return leftOperand_int < rightOperand_int;
2744 else
2745 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2746 : lstrcmpW (leftOperand, rightOperand) < 0;
2749 if (!lstrcmpiW(operator, L"leq")) {
2750 if (int_operands)
2751 return leftOperand_int <= rightOperand_int;
2752 else
2753 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2754 : lstrcmpW (leftOperand, rightOperand) <= 0;
2757 if (!lstrcmpiW(operator, L"equ")) {
2758 if (int_operands)
2759 return leftOperand_int == rightOperand_int;
2760 else
2761 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2762 : lstrcmpW (leftOperand, rightOperand) == 0;
2765 if (!lstrcmpiW(operator, L"neq")) {
2766 if (int_operands)
2767 return leftOperand_int != rightOperand_int;
2768 else
2769 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2770 : lstrcmpW (leftOperand, rightOperand) != 0;
2773 if (!lstrcmpiW(operator, L"geq")) {
2774 if (int_operands)
2775 return leftOperand_int >= rightOperand_int;
2776 else
2777 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2778 : lstrcmpW (leftOperand, rightOperand) >= 0;
2781 if (!lstrcmpiW(operator, L"gtr")) {
2782 if (int_operands)
2783 return leftOperand_int > rightOperand_int;
2784 else
2785 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2786 : lstrcmpW (leftOperand, rightOperand) > 0;
2789 return -1;
2792 int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate)
2794 WCHAR condition[MAX_PATH];
2795 int caseInsensitive = (wcsstr(quals, L"/I") != NULL);
2797 *negate = !lstrcmpiW(param1,L"not");
2798 lstrcpyW(condition, (*negate ? param2 : param1));
2799 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2801 if (!lstrcmpiW(condition, L"errorlevel")) {
2802 WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
2803 WCHAR *endptr;
2804 long int param_int = wcstol(param, &endptr, 10);
2805 if (*endptr) goto syntax_err;
2806 *test = ((long int)errorlevel >= param_int);
2807 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2809 else if (!lstrcmpiW(condition, L"exist")) {
2810 WIN32_FIND_DATAW fd;
2811 HANDLE hff;
2812 WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
2813 int len = lstrlenW(param);
2815 if (!len) goto syntax_err;
2816 /* FindFirstFile does not like a directory path ending in '\', append a '.' */
2817 if (param[len-1] == '\\') lstrcatW(param, L".");
2819 hff = FindFirstFileW(param, &fd);
2820 *test = (hff != INVALID_HANDLE_VALUE );
2821 if (*test) FindClose(hff);
2823 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2825 else if (!lstrcmpiW(condition, L"defined")) {
2826 *test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE),
2827 NULL, 0) > 0);
2828 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2830 else { /* comparison operation */
2831 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2832 WCHAR *paramStart;
2834 lstrcpyW(leftOperand, WCMD_parameter(p, (*negate)+caseInsensitive, &paramStart, TRUE, FALSE));
2835 if (!*leftOperand)
2836 goto syntax_err;
2838 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2839 p = paramStart + lstrlenW(leftOperand);
2840 while (*p == ' ' || *p == '\t')
2841 p++;
2843 if (!wcsncmp(p, L"==", lstrlenW(L"==")))
2844 lstrcpyW(operator, L"==");
2845 else {
2846 lstrcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2847 if (!*operator) goto syntax_err;
2849 p += lstrlenW(operator);
2851 lstrcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2852 if (!*rightOperand)
2853 goto syntax_err;
2855 *test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2856 if (*test == -1)
2857 goto syntax_err;
2859 p = paramStart + lstrlenW(rightOperand);
2860 WCMD_parameter(p, 0, command, FALSE, FALSE);
2863 return 1;
2865 syntax_err:
2866 return -1;
2869 /****************************************************************************
2870 * WCMD_if
2872 * Batch file conditional.
2874 * On entry, cmdlist will point to command containing the IF, and optionally
2875 * the first command to execute (if brackets not found)
2876 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2877 * If ('s were found, execute all within that bracket
2878 * Command may optionally be followed by an ELSE - need to skip instructions
2879 * in the else using the same logic
2881 * FIXME: Much more syntax checking needed!
2883 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2885 int negate; /* Negate condition */
2886 int test; /* Condition evaluation result */
2887 WCHAR *command;
2889 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
2890 set in a call to WCMD_parse before */
2891 if (evaluate_if_condition(p, &command, &test, &negate) == -1)
2892 goto syntax_err;
2894 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n",
2895 wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
2896 wine_dbgstr_w(param2), wine_dbgstr_w(command));
2898 /* Process rest of IF statement which is on the same line
2899 Note: This may process all or some of the cmdList (eg a GOTO) */
2900 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2901 return;
2903 syntax_err:
2904 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2907 /****************************************************************************
2908 * WCMD_move
2910 * Move a file, directory tree or wildcarded set of files.
2913 void WCMD_move (void)
2915 BOOL status;
2916 WIN32_FIND_DATAW fd;
2917 HANDLE hff;
2918 WCHAR input[MAX_PATH];
2919 WCHAR output[MAX_PATH];
2920 WCHAR drive[10];
2921 WCHAR dir[MAX_PATH];
2922 WCHAR fname[MAX_PATH];
2923 WCHAR ext[MAX_PATH];
2925 if (param1[0] == 0x00) {
2926 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2927 return;
2930 /* If no destination supplied, assume current directory */
2931 if (param2[0] == 0x00) {
2932 lstrcpyW(param2, L".");
2935 /* If 2nd parm is directory, then use original filename */
2936 /* Convert partial path to full path */
2937 if (!WCMD_get_fullpath(param1, ARRAY_SIZE(input), input, NULL) ||
2938 !WCMD_get_fullpath(param2, ARRAY_SIZE(output), output, NULL)) return;
2939 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2940 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2942 /* Split into components */
2943 _wsplitpath(input, drive, dir, fname, ext);
2945 hff = FindFirstFileW(input, &fd);
2946 if (hff == INVALID_HANDLE_VALUE)
2947 return;
2949 do {
2950 WCHAR dest[MAX_PATH];
2951 WCHAR src[MAX_PATH];
2952 DWORD attribs;
2953 BOOL ok = TRUE;
2954 DWORD flags = 0;
2956 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2958 /* Build src & dest name */
2959 lstrcpyW(src, drive);
2960 lstrcatW(src, dir);
2962 /* See if dest is an existing directory */
2963 attribs = GetFileAttributesW(output);
2964 if (attribs != INVALID_FILE_ATTRIBUTES &&
2965 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2966 lstrcpyW(dest, output);
2967 lstrcatW(dest, L"\\");
2968 lstrcatW(dest, fd.cFileName);
2969 } else {
2970 lstrcpyW(dest, output);
2973 lstrcatW(src, fd.cFileName);
2975 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2976 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2978 /* If destination exists, prompt unless /Y supplied */
2979 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2980 BOOL force = FALSE;
2981 WCHAR copycmd[MAXSTRING];
2982 DWORD len;
2984 /* Default whether automatic overwriting is on. If we are interactive then
2985 we prompt by default, otherwise we overwrite by default
2986 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2987 if (wcsstr(quals, L"/-Y"))
2988 force = FALSE;
2989 else if (wcsstr(quals, L"/Y"))
2990 force = TRUE;
2991 else {
2992 /* By default, we will force the overwrite in batch mode and ask for
2993 * confirmation in interactive mode. */
2994 force = !interactive;
2995 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
2996 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
2997 * default behavior. */
2998 len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
2999 if (len && len < ARRAY_SIZE(copycmd)) {
3000 if (!lstrcmpiW(copycmd, L"/Y"))
3001 force = TRUE;
3002 else if (!lstrcmpiW(copycmd, L"/-Y"))
3003 force = FALSE;
3007 /* Prompt if overwriting */
3008 if (!force) {
3009 WCHAR* question;
3011 /* Ask for confirmation */
3012 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
3013 ok = WCMD_ask_confirm(question, FALSE, NULL);
3014 LocalFree(question);
3017 if (ok)
3018 flags |= MOVEFILE_REPLACE_EXISTING;
3021 if (ok) {
3022 status = MoveFileExW(src, dest, flags);
3023 } else {
3024 status = TRUE;
3027 if (!status) {
3028 WCMD_print_error ();
3029 errorlevel = 1;
3031 } while (FindNextFileW(hff, &fd) != 0);
3033 FindClose(hff);
3036 /****************************************************************************
3037 * WCMD_pause
3039 * Suspend execution of a batch script until a key is typed
3042 void WCMD_pause (void)
3044 DWORD oldmode;
3045 BOOL have_console;
3046 DWORD count;
3047 WCHAR key;
3048 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
3050 have_console = GetConsoleMode(hIn, &oldmode);
3051 if (have_console)
3052 SetConsoleMode(hIn, 0);
3054 WCMD_output_asis(anykey);
3055 WCMD_ReadFile(hIn, &key, 1, &count);
3056 if (have_console)
3057 SetConsoleMode(hIn, oldmode);
3060 /****************************************************************************
3061 * WCMD_remove_dir
3063 * Delete a directory.
3066 void WCMD_remove_dir (WCHAR *args) {
3068 int argno = 0;
3069 int argsProcessed = 0;
3070 WCHAR *argN = args;
3072 /* Loop through all args */
3073 while (argN) {
3074 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3075 if (argN && argN[0] != '/') {
3076 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
3077 wine_dbgstr_w(quals));
3078 argsProcessed++;
3080 /* If subdirectory search not supplied, just try to remove
3081 and report error if it fails (eg if it contains a file) */
3082 if (wcsstr(quals, L"/S") == NULL) {
3083 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
3085 /* Otherwise use ShFileOp to recursively remove a directory */
3086 } else {
3088 SHFILEOPSTRUCTW lpDir;
3090 /* Ask first */
3091 if (wcsstr(quals, L"/Q") == NULL) {
3092 BOOL ok;
3093 WCHAR question[MAXSTRING];
3095 /* Ask for confirmation */
3096 wsprintfW(question, L"%s ", thisArg);
3097 ok = WCMD_ask_confirm(question, TRUE, NULL);
3099 /* Abort if answer is 'N' */
3100 if (!ok) return;
3103 /* Do the delete */
3104 lpDir.hwnd = NULL;
3105 lpDir.pTo = NULL;
3106 lpDir.pFrom = thisArg;
3107 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
3108 lpDir.wFunc = FO_DELETE;
3110 /* SHFileOperationW needs file list with a double null termination */
3111 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3113 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3118 /* Handle no valid args */
3119 if (argsProcessed == 0) {
3120 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3121 return;
3126 /****************************************************************************
3127 * WCMD_rename
3129 * Rename a file.
3132 void WCMD_rename (void)
3134 BOOL status;
3135 HANDLE hff;
3136 WIN32_FIND_DATAW fd;
3137 WCHAR input[MAX_PATH];
3138 WCHAR *dotDst = NULL;
3139 WCHAR drive[10];
3140 WCHAR dir[MAX_PATH];
3141 WCHAR fname[MAX_PATH];
3142 WCHAR ext[MAX_PATH];
3144 errorlevel = 0;
3146 /* Must be at least two args */
3147 if (param1[0] == 0x00 || param2[0] == 0x00) {
3148 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3149 errorlevel = 1;
3150 return;
3153 /* Destination cannot contain a drive letter or directory separator */
3154 if ((wcschr(param2,':') != NULL) || (wcschr(param2,'\\') != NULL)) {
3155 SetLastError(ERROR_INVALID_PARAMETER);
3156 WCMD_print_error();
3157 errorlevel = 1;
3158 return;
3161 /* Convert partial path to full path */
3162 if (!WCMD_get_fullpath(param1, ARRAY_SIZE(input), input, NULL)) return;
3163 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3164 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3165 dotDst = wcschr(param2, '.');
3167 /* Split into components */
3168 _wsplitpath(input, drive, dir, fname, ext);
3170 hff = FindFirstFileW(input, &fd);
3171 if (hff == INVALID_HANDLE_VALUE)
3172 return;
3174 do {
3175 WCHAR dest[MAX_PATH];
3176 WCHAR src[MAX_PATH];
3177 WCHAR *dotSrc = NULL;
3178 int dirLen;
3180 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3182 /* FIXME: If dest name or extension is *, replace with filename/ext
3183 part otherwise use supplied name. This supports:
3184 ren *.fred *.jim
3185 ren jim.* fred.* etc
3186 However, windows has a more complex algorithm supporting eg
3187 ?'s and *'s mid name */
3188 dotSrc = wcschr(fd.cFileName, '.');
3190 /* Build src & dest name */
3191 lstrcpyW(src, drive);
3192 lstrcatW(src, dir);
3193 lstrcpyW(dest, src);
3194 dirLen = lstrlenW(src);
3195 lstrcatW(src, fd.cFileName);
3197 /* Build name */
3198 if (param2[0] == '*') {
3199 lstrcatW(dest, fd.cFileName);
3200 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3201 } else {
3202 lstrcatW(dest, param2);
3203 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3206 /* Build Extension */
3207 if (dotDst && (*(dotDst+1)=='*')) {
3208 if (dotSrc) lstrcatW(dest, dotSrc);
3209 } else if (dotDst) {
3210 lstrcatW(dest, dotDst);
3213 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3214 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3216 status = MoveFileW(src, dest);
3218 if (!status) {
3219 WCMD_print_error ();
3220 errorlevel = 1;
3222 } while (FindNextFileW(hff, &fd) != 0);
3224 FindClose(hff);
3227 /*****************************************************************************
3228 * WCMD_dupenv
3230 * Make a copy of the environment.
3232 static WCHAR *WCMD_dupenv( const WCHAR *env )
3234 WCHAR *env_copy;
3235 int len;
3237 if( !env )
3238 return NULL;
3240 len = 0;
3241 while ( env[len] )
3242 len += (lstrlenW(&env[len]) + 1);
3244 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3245 if (!env_copy)
3247 WINE_ERR("out of memory\n");
3248 return env_copy;
3250 memcpy (env_copy, env, len*sizeof (WCHAR));
3251 env_copy[len] = 0;
3253 return env_copy;
3256 /*****************************************************************************
3257 * WCMD_setlocal
3259 * setlocal pushes the environment onto a stack
3260 * Save the environment as unicode so we don't screw anything up.
3262 void WCMD_setlocal (const WCHAR *s) {
3263 WCHAR *env;
3264 struct env_stack *env_copy;
3265 WCHAR cwd[MAX_PATH];
3266 BOOL newdelay;
3268 /* setlocal does nothing outside of batch programs */
3269 if (!context) return;
3271 /* DISABLEEXTENSIONS ignored */
3273 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3274 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3275 if (!wcsicmp(param1, L"ENABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"ENABLEDELAYEDEXPANSION")) {
3276 newdelay = TRUE;
3277 } else if (!wcsicmp(param1, L"DISABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"DISABLEDELAYEDEXPANSION")) {
3278 newdelay = FALSE;
3279 } else {
3280 newdelay = delayedsubst;
3282 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3284 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3285 if( !env_copy )
3287 WINE_ERR ("out of memory\n");
3288 return;
3291 env = GetEnvironmentStringsW ();
3292 env_copy->strings = WCMD_dupenv (env);
3293 if (env_copy->strings)
3295 env_copy->batchhandle = context->h;
3296 env_copy->next = saved_environment;
3297 env_copy->delayedsubst = delayedsubst;
3298 delayedsubst = newdelay;
3299 saved_environment = env_copy;
3301 /* Save the current drive letter */
3302 GetCurrentDirectoryW(MAX_PATH, cwd);
3303 env_copy->u.cwd = cwd[0];
3305 else
3306 LocalFree (env_copy);
3308 FreeEnvironmentStringsW (env);
3312 /*****************************************************************************
3313 * WCMD_endlocal
3315 * endlocal pops the environment off a stack
3316 * Note: When searching for '=', search from WCHAR position 1, to handle
3317 * special internal environment variables =C:, =D: etc
3319 void WCMD_endlocal (void) {
3320 WCHAR *env, *old, *p;
3321 struct env_stack *temp;
3322 int len, n;
3324 /* setlocal does nothing outside of batch programs */
3325 if (!context) return;
3327 /* setlocal needs a saved environment from within the same context (batch
3328 program) as it was saved in */
3329 if (!saved_environment || saved_environment->batchhandle != context->h)
3330 return;
3332 /* pop the old environment from the stack */
3333 temp = saved_environment;
3334 saved_environment = temp->next;
3336 /* delete the current environment, totally */
3337 env = GetEnvironmentStringsW ();
3338 old = WCMD_dupenv (env);
3339 len = 0;
3340 while (old[len]) {
3341 n = lstrlenW(&old[len]) + 1;
3342 p = wcschr(&old[len] + 1, '=');
3343 if (p)
3345 *p++ = 0;
3346 SetEnvironmentVariableW (&old[len], NULL);
3348 len += n;
3350 LocalFree (old);
3351 FreeEnvironmentStringsW (env);
3353 /* restore old environment */
3354 env = temp->strings;
3355 len = 0;
3356 delayedsubst = temp->delayedsubst;
3357 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3358 while (env[len]) {
3359 n = lstrlenW(&env[len]) + 1;
3360 p = wcschr(&env[len] + 1, '=');
3361 if (p)
3363 *p++ = 0;
3364 SetEnvironmentVariableW (&env[len], p);
3366 len += n;
3369 /* Restore current drive letter */
3370 if (IsCharAlphaW(temp->u.cwd)) {
3371 WCHAR envvar[4];
3372 WCHAR cwd[MAX_PATH];
3374 wsprintfW(envvar, L"=%c:", temp->u.cwd);
3375 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3376 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3377 SetCurrentDirectoryW(cwd);
3381 LocalFree (env);
3382 LocalFree (temp);
3385 /*****************************************************************************
3386 * WCMD_setshow_default
3388 * Set/Show the current default directory
3391 void WCMD_setshow_default (const WCHAR *args) {
3393 BOOL status;
3394 WCHAR string[1024];
3395 WCHAR cwd[1024];
3396 WCHAR *pos;
3397 WIN32_FIND_DATAW fd;
3398 HANDLE hff;
3400 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3402 /* Skip /D and trailing whitespace if on the front of the command line */
3403 if (lstrlenW(args) >= 2 &&
3404 CompareStringW(LOCALE_USER_DEFAULT,
3405 NORM_IGNORECASE | SORT_STRINGSORT,
3406 args, 2, L"/D", -1) == CSTR_EQUAL) {
3407 args += 2;
3408 while (*args && (*args==' ' || *args=='\t'))
3409 args++;
3412 GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
3414 if (!*args) {
3415 lstrcatW(cwd, L"\r\n");
3416 WCMD_output_asis (cwd);
3418 else {
3419 /* Remove any double quotes, which may be in the
3420 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3421 pos = string;
3422 while (*args) {
3423 if (*args != '"') *pos++ = *args;
3424 args++;
3426 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3427 pos--;
3428 *pos = 0x00;
3430 /* Search for appropriate directory */
3431 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3432 hff = FindFirstFileW(string, &fd);
3433 if (hff != INVALID_HANDLE_VALUE) {
3434 do {
3435 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3436 WCHAR fpath[MAX_PATH];
3437 WCHAR drive[10];
3438 WCHAR dir[MAX_PATH];
3439 WCHAR fname[MAX_PATH];
3440 WCHAR ext[MAX_PATH];
3442 /* Convert path into actual directory spec */
3443 if (!WCMD_get_fullpath(string, ARRAY_SIZE(fpath), fpath, NULL)) return;
3444 _wsplitpath(fpath, drive, dir, fname, ext);
3446 /* Rebuild path */
3447 wsprintfW(string, L"%s%s%s", drive, dir, fd.cFileName);
3448 break;
3450 } while (FindNextFileW(hff, &fd) != 0);
3451 FindClose(hff);
3454 /* Change to that directory */
3455 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3457 status = SetCurrentDirectoryW(string);
3458 if (!status) {
3459 errorlevel = 1;
3460 WCMD_print_error ();
3461 return;
3462 } else {
3464 /* Save away the actual new directory, to store as current location */
3465 GetCurrentDirectoryW(ARRAY_SIZE(string), string);
3467 /* Restore old directory if drive letter would change, and
3468 CD x:\directory /D (or pushd c:\directory) not supplied */
3469 if ((wcsstr(quals, L"/D") == NULL) &&
3470 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3471 SetCurrentDirectoryW(cwd);
3475 /* Set special =C: type environment variable, for drive letter of
3476 change of directory, even if path was restored due to missing
3477 /D (allows changing drive letter when not resident on that
3478 drive */
3479 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3480 WCHAR env[4];
3481 lstrcpyW(env, L"=");
3482 memcpy(env+1, string, 2 * sizeof(WCHAR));
3483 env[3] = 0x00;
3484 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3485 SetEnvironmentVariableW(env, string);
3489 return;
3492 /****************************************************************************
3493 * WCMD_setshow_date
3495 * Set/Show the system date
3496 * FIXME: Can't change date yet
3499 void WCMD_setshow_date (void) {
3501 WCHAR curdate[64], buffer[64];
3502 DWORD count;
3504 if (!*param1) {
3505 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, curdate, ARRAY_SIZE(curdate))) {
3506 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3507 if (wcsstr(quals, L"/T") == NULL) {
3508 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3509 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
3510 if (count > 2) {
3511 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3515 else WCMD_print_error ();
3517 else {
3518 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3522 /****************************************************************************
3523 * WCMD_compare
3524 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3525 * the equals sign.
3527 static int __cdecl WCMD_compare( const void *a, const void *b )
3529 int r;
3530 const WCHAR * const *str_a = a, * const *str_b = b;
3531 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3532 *str_a, wcscspn(*str_a, L"="), *str_b, wcscspn(*str_b, L"=") );
3533 if( r == CSTR_LESS_THAN ) return -1;
3534 if( r == CSTR_GREATER_THAN ) return 1;
3535 return 0;
3538 /****************************************************************************
3539 * WCMD_setshow_sortenv
3541 * sort variables into order for display
3542 * Optionally only display those who start with a stub
3543 * returns the count displayed
3545 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3547 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3548 const WCHAR **str;
3550 if (stub) stublen = lstrlenW(stub);
3552 /* count the number of strings, and the total length */
3553 while ( s[len] ) {
3554 len += (lstrlenW(&s[len]) + 1);
3555 count++;
3558 /* add the strings to an array */
3559 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3560 if( !str )
3561 return 0;
3562 str[0] = s;
3563 for( i=1; i<count; i++ )
3564 str[i] = str[i-1] + lstrlenW(str[i-1]) + 1;
3566 /* sort the array */
3567 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3569 /* print it */
3570 for( i=0; i<count; i++ ) {
3571 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3572 NORM_IGNORECASE | SORT_STRINGSORT,
3573 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3574 /* Don't display special internal variables */
3575 if (str[i][0] != '=') {
3576 WCMD_output_asis(str[i]);
3577 WCMD_output_asis(L"\r\n");
3578 displayedcount++;
3583 LocalFree( str );
3584 return displayedcount;
3587 /****************************************************************************
3588 * WCMD_getprecedence
3589 * Return the precedence of a particular operator
3591 static int WCMD_getprecedence(const WCHAR in)
3593 switch (in) {
3594 case '!':
3595 case '~':
3596 case OP_POSITIVE:
3597 case OP_NEGATIVE:
3598 return 8;
3599 case '*':
3600 case '/':
3601 case '%':
3602 return 7;
3603 case '+':
3604 case '-':
3605 return 6;
3606 case '<':
3607 case '>':
3608 return 5;
3609 case '&':
3610 return 4;
3611 case '^':
3612 return 3;
3613 case '|':
3614 return 2;
3615 case '=':
3616 case OP_ASSSIGNMUL:
3617 case OP_ASSSIGNDIV:
3618 case OP_ASSSIGNMOD:
3619 case OP_ASSSIGNADD:
3620 case OP_ASSSIGNSUB:
3621 case OP_ASSSIGNAND:
3622 case OP_ASSSIGNNOT:
3623 case OP_ASSSIGNOR:
3624 case OP_ASSSIGNSHL:
3625 case OP_ASSSIGNSHR:
3626 return 1;
3627 default:
3628 return 0;
3632 /****************************************************************************
3633 * WCMD_pushnumber
3634 * Push either a number or name (environment variable) onto the supplied
3635 * stack
3637 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3638 VARSTACK *thisstack = heap_xalloc(sizeof(VARSTACK));
3639 thisstack->isnum = (var == NULL);
3640 if (var) {
3641 thisstack->variable = var;
3642 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3643 } else {
3644 thisstack->value = num;
3645 WINE_TRACE("Pushed number %d\n", num);
3647 thisstack->next = *varstack;
3648 *varstack = thisstack;
3651 /****************************************************************************
3652 * WCMD_peeknumber
3653 * Returns the value of the top number or environment variable on the stack
3654 * and leaves the item on the stack.
3656 static int WCMD_peeknumber(VARSTACK **varstack) {
3657 int result = 0;
3658 VARSTACK *thisvar;
3660 if (varstack) {
3661 thisvar = *varstack;
3662 if (!thisvar->isnum) {
3663 WCHAR tmpstr[MAXSTRING];
3664 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3665 result = wcstol(tmpstr,NULL,0);
3667 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3668 } else {
3669 result = thisvar->value;
3672 WINE_TRACE("Peeked number %d\n", result);
3673 return result;
3676 /****************************************************************************
3677 * WCMD_popnumber
3678 * Returns the value of the top number or environment variable on the stack
3679 * and removes the item from the stack.
3681 static int WCMD_popnumber(VARSTACK **varstack) {
3682 int result = 0;
3683 VARSTACK *thisvar;
3685 if (varstack) {
3686 thisvar = *varstack;
3687 result = WCMD_peeknumber(varstack);
3688 if (!thisvar->isnum) heap_free(thisvar->variable);
3689 *varstack = thisvar->next;
3690 heap_free(thisvar);
3692 WINE_TRACE("Popped number %d\n", result);
3693 return result;
3696 /****************************************************************************
3697 * WCMD_pushoperator
3698 * Push an operator onto the supplied stack
3700 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3701 OPSTACK *thisstack = heap_xalloc(sizeof(OPSTACK));
3702 thisstack->precedence = precedence;
3703 thisstack->op = op;
3704 thisstack->next = *opstack;
3705 WINE_TRACE("Pushed operator %c\n", op);
3706 *opstack = thisstack;
3709 /****************************************************************************
3710 * WCMD_popoperator
3711 * Returns the operator from the top of the stack and removes the item from
3712 * the stack.
3714 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3715 WCHAR result = 0;
3716 OPSTACK *thisop;
3718 if (opstack) {
3719 thisop = *opstack;
3720 result = thisop->op;
3721 *opstack = thisop->next;
3722 heap_free(thisop);
3724 WINE_TRACE("Popped operator %c\n", result);
3725 return result;
3728 /****************************************************************************
3729 * WCMD_reduce
3730 * Actions the top operator on the stack against the first and sometimes
3731 * second value on the variable stack, and pushes the result
3732 * Returns non-zero on error.
3734 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3735 WCHAR thisop;
3736 int var1,var2;
3737 int rc = 0;
3739 if (!*opstack || !*varstack) {
3740 WINE_TRACE("No operators for the reduce\n");
3741 return WCMD_NOOPERATOR;
3744 /* Remove the top operator */
3745 thisop = WCMD_popoperator(opstack);
3746 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
3748 /* One variable operators */
3749 var1 = WCMD_popnumber(varstack);
3750 switch (thisop) {
3751 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3752 break;
3753 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3754 break;
3755 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3756 break;
3757 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3758 break;
3761 /* Two variable operators */
3762 if (!*varstack) {
3763 WINE_TRACE("No operands left for the reduce?\n");
3764 return WCMD_NOOPERAND;
3766 switch (thisop) {
3767 case '!':
3768 case '~':
3769 case OP_POSITIVE:
3770 case OP_NEGATIVE:
3771 break; /* Handled above */
3772 case '*': var2 = WCMD_popnumber(varstack);
3773 WCMD_pushnumber(NULL, var2*var1, varstack);
3774 break;
3775 case '/': var2 = WCMD_popnumber(varstack);
3776 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3777 WCMD_pushnumber(NULL, var2/var1, varstack);
3778 break;
3779 case '+': var2 = WCMD_popnumber(varstack);
3780 WCMD_pushnumber(NULL, var2+var1, varstack);
3781 break;
3782 case '-': var2 = WCMD_popnumber(varstack);
3783 WCMD_pushnumber(NULL, var2-var1, varstack);
3784 break;
3785 case '&': var2 = WCMD_popnumber(varstack);
3786 WCMD_pushnumber(NULL, var2&var1, varstack);
3787 break;
3788 case '%': var2 = WCMD_popnumber(varstack);
3789 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3790 WCMD_pushnumber(NULL, var2%var1, varstack);
3791 break;
3792 case '^': var2 = WCMD_popnumber(varstack);
3793 WCMD_pushnumber(NULL, var2^var1, varstack);
3794 break;
3795 case '<': var2 = WCMD_popnumber(varstack);
3796 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3797 which differs from the compiler (for example gcc) so being explicit. */
3798 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3799 WCMD_pushnumber(NULL, 0, varstack);
3800 } else {
3801 WCMD_pushnumber(NULL, var2<<var1, varstack);
3803 break;
3804 case '>': var2 = WCMD_popnumber(varstack);
3805 WCMD_pushnumber(NULL, var2>>var1, varstack);
3806 break;
3807 case '|': var2 = WCMD_popnumber(varstack);
3808 WCMD_pushnumber(NULL, var2|var1, varstack);
3809 break;
3811 case OP_ASSSIGNMUL:
3812 case OP_ASSSIGNDIV:
3813 case OP_ASSSIGNMOD:
3814 case OP_ASSSIGNADD:
3815 case OP_ASSSIGNSUB:
3816 case OP_ASSSIGNAND:
3817 case OP_ASSSIGNNOT:
3818 case OP_ASSSIGNOR:
3819 case OP_ASSSIGNSHL:
3820 case OP_ASSSIGNSHR:
3822 int i = 0;
3824 /* The left of an equals must be one variable */
3825 if (!(*varstack) || (*varstack)->isnum) {
3826 return WCMD_NOOPERAND;
3829 /* Make the number stack grow by inserting the value of the variable */
3830 var2 = WCMD_peeknumber(varstack);
3831 WCMD_pushnumber(NULL, var2, varstack);
3832 WCMD_pushnumber(NULL, var1, varstack);
3834 /* Make the operand stack grow by pushing the assign operator plus the
3835 operator to perform */
3836 while (calcassignments[i].op != ' ' &&
3837 calcassignments[i].calculatedop != thisop) {
3838 i++;
3840 if (calcassignments[i].calculatedop == ' ') {
3841 WINE_ERR("Unexpected operator %c\n", thisop);
3842 return WCMD_NOOPERATOR;
3844 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3845 WCMD_pushoperator(calcassignments[i].op,
3846 WCMD_getprecedence(calcassignments[i].op), opstack);
3847 break;
3850 case '=':
3852 WCHAR result[MAXSTRING];
3854 /* Build the result, then push it onto the stack */
3855 swprintf(result, ARRAY_SIZE(result), L"%d", var1);
3856 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3857 wine_dbgstr_w(result));
3858 SetEnvironmentVariableW((*varstack)->variable, result);
3859 var2 = WCMD_popnumber(varstack);
3860 WCMD_pushnumber(NULL, var1, varstack);
3861 break;
3864 default: WINE_ERR("Unrecognized operator %c\n", thisop);
3867 return rc;
3871 /****************************************************************************
3872 * WCMD_handleExpression
3873 * Handles an expression provided to set /a - If it finds brackets, it uses
3874 * recursion to process the parts in brackets.
3876 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3878 static const WCHAR mathDelims[] = L" \t()!~-*/%+<>&^|=,";
3879 int rc = 0;
3880 WCHAR *pos;
3881 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3882 OPSTACK *opstackhead = NULL;
3883 VARSTACK *varstackhead = NULL;
3884 WCHAR foundhalf = 0;
3886 /* Initialize */
3887 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3888 pos = *expr;
3890 /* Iterate through until whole expression is processed */
3891 while (pos && *pos) {
3892 BOOL treatasnumber;
3894 /* Skip whitespace to get to the next character to process*/
3895 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3896 if (!*pos) goto exprreturn;
3898 /* If we have found anything other than an operator then it's a number/variable */
3899 if (wcschr(mathDelims, *pos) == NULL) {
3900 WCHAR *parmstart, *parm, *dupparm;
3901 WCHAR *nextpos;
3903 /* Cannot have an expression with var/number twice, without an operator
3904 in-between, nor or number following a half constructed << or >> operator */
3905 if (lastwasnumber || foundhalf) {
3906 rc = WCMD_NOOPERATOR;
3907 goto exprerrorreturn;
3909 lastwasnumber = TRUE;
3911 if (iswdigit(*pos)) {
3912 /* For a number - just push it onto the stack */
3913 int num = wcstoul(pos, &nextpos, 0);
3914 WCMD_pushnumber(NULL, num, &varstackhead);
3915 pos = nextpos;
3917 /* Verify the number was validly formed */
3918 if (*nextpos && (wcschr(mathDelims, *nextpos) == NULL)) {
3919 rc = WCMD_BADHEXOCT;
3920 goto exprerrorreturn;
3922 } else {
3924 /* For a variable - just push it onto the stack */
3925 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3926 dupparm = heap_strdupW(parm);
3927 WCMD_pushnumber(dupparm, 0, &varstackhead);
3928 pos = parmstart + lstrlenW(dupparm);
3930 continue;
3933 /* We have found an operator. Some operators are one character, some two, and the minus
3934 and plus signs need special processing as they can be either operators or just influence
3935 the parameter which follows them */
3936 if (foundhalf && (*pos != foundhalf)) {
3937 /* Badly constructed operator pair */
3938 rc = WCMD_NOOPERATOR;
3939 goto exprerrorreturn;
3942 treatasnumber = FALSE; /* We are processing an operand */
3943 switch (*pos) {
3945 /* > and < are special as they are double character operators (and spaces can be between them!)
3946 If we see these for the first time, set a flag, and second time around we continue.
3947 Note these double character operators are stored as just one of the characters on the stack */
3948 case '>':
3949 case '<': if (!foundhalf) {
3950 foundhalf = *pos;
3951 pos++;
3952 break;
3954 /* We have found the rest, so clear up the knowledge of the half completed part and
3955 drop through to normal operator processing */
3956 foundhalf = 0;
3957 /* drop through */
3959 case '=': if (*pos=='=') {
3960 /* = is special cased as if the last was an operator then we may have e.g. += or
3961 *= etc which we need to handle by replacing the operator that is on the stack
3962 with a calculated assignment equivalent */
3963 if (!lastwasnumber && opstackhead) {
3964 int i = 0;
3965 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3966 i++;
3968 if (calcassignments[i].op == ' ') {
3969 rc = WCMD_NOOPERAND;
3970 goto exprerrorreturn;
3971 } else {
3972 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3973 when the general operator handling happens further down. */
3974 *pos = calcassignments[i].calculatedop;
3975 WCMD_popoperator(&opstackhead);
3979 /* Drop though */
3981 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3982 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3983 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
3984 /* drop through */
3985 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
3986 /* drop through */
3988 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3989 case '!': /* drop through */
3990 case '~': /* drop through */
3991 case '/': /* drop through */
3992 case '%': /* drop through */
3993 case '&': /* drop through */
3994 case '^': /* drop through */
3995 case '*': /* drop through */
3996 case '|':
3997 /* General code for handling most of the operators - look at the
3998 precedence of the top item on the stack, and see if we need to
3999 action the stack before we push something else onto it. */
4001 int precedence = WCMD_getprecedence(*pos);
4002 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
4003 precedence, !opstackhead?-1:opstackhead->precedence);
4005 /* In general, for things with the same precedence, reduce immediately
4006 except for assignments and unary operators which do not */
4007 while (!rc && opstackhead &&
4008 ((opstackhead->precedence > precedence) ||
4009 ((opstackhead->precedence == precedence) &&
4010 (precedence != 1) && (precedence != 8)))) {
4011 rc = WCMD_reduce(&opstackhead, &varstackhead);
4013 if (rc) goto exprerrorreturn;
4014 WCMD_pushoperator(*pos, precedence, &opstackhead);
4015 pos++;
4016 break;
4019 /* comma means start a new expression, ie calculate what we have */
4020 case ',':
4022 int prevresult = -1;
4023 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
4024 while (!rc && opstackhead) {
4025 rc = WCMD_reduce(&opstackhead, &varstackhead);
4027 if (rc) goto exprerrorreturn;
4028 /* If we have anything other than one number left, error
4029 otherwise throw the number away */
4030 if (!varstackhead || varstackhead->next) {
4031 rc = WCMD_NOOPERATOR;
4032 goto exprerrorreturn;
4034 prevresult = WCMD_popnumber(&varstackhead);
4035 WINE_TRACE("Expression resolved to %d\n", prevresult);
4036 heap_free(varstackhead);
4037 varstackhead = NULL;
4038 pos++;
4039 break;
4042 /* Open bracket - use iteration to parse the inner expression, then continue */
4043 case '(' : {
4044 int exprresult = 0;
4045 pos++;
4046 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
4047 if (rc) goto exprerrorreturn;
4048 WCMD_pushnumber(NULL, exprresult, &varstackhead);
4049 break;
4052 /* Close bracket - we have finished this depth, calculate and return */
4053 case ')' : {
4054 pos++;
4055 treatasnumber = TRUE; /* Things in brackets result in a number */
4056 if (depth == 0) {
4057 rc = WCMD_BADPAREN;
4058 goto exprerrorreturn;
4060 goto exprreturn;
4063 default:
4064 WINE_ERR("Unrecognized operator %c\n", *pos);
4065 pos++;
4067 lastwasnumber = treatasnumber;
4070 exprreturn:
4071 *expr = pos;
4073 /* We need to reduce until we have a single number (or variable) on the
4074 stack and set the return value to that */
4075 while (!rc && opstackhead) {
4076 rc = WCMD_reduce(&opstackhead, &varstackhead);
4078 if (rc) goto exprerrorreturn;
4080 /* If we have anything other than one number left, error
4081 otherwise throw the number away */
4082 if (!varstackhead || varstackhead->next) {
4083 rc = WCMD_NOOPERATOR;
4084 goto exprerrorreturn;
4087 /* Now get the number (and convert if it's just a variable name) */
4088 *ret = WCMD_popnumber(&varstackhead);
4090 exprerrorreturn:
4091 /* Free all remaining memory */
4092 while (opstackhead) WCMD_popoperator(&opstackhead);
4093 while (varstackhead) WCMD_popnumber(&varstackhead);
4095 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
4096 return rc;
4099 /****************************************************************************
4100 * WCMD_setshow_env
4102 * Set/Show the environment variables
4105 void WCMD_setshow_env (WCHAR *s) {
4107 LPVOID env;
4108 WCHAR *p;
4109 BOOL status;
4110 WCHAR string[MAXSTRING];
4112 if (param1[0] == 0x00 && quals[0] == 0x00) {
4113 env = GetEnvironmentStringsW();
4114 WCMD_setshow_sortenv( env, NULL );
4115 return;
4118 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4119 if (CompareStringW(LOCALE_USER_DEFAULT,
4120 NORM_IGNORECASE | SORT_STRINGSORT,
4121 s, 2, L"/P", -1) == CSTR_EQUAL) {
4122 DWORD count;
4124 s += 2;
4125 while (*s && (*s==' ' || *s=='\t')) s++;
4126 /* set /P "var=value"jim ignores anything after the last quote */
4127 if (*s=='\"') {
4128 WCHAR *lastquote;
4129 lastquote = WCMD_strip_quotes(s);
4130 if (lastquote) *lastquote = 0x00;
4131 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4134 /* If no parameter, or no '=' sign, return an error */
4135 if (!(*s) || ((p = wcschr (s, '=')) == NULL )) {
4136 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4137 return;
4140 /* Output the prompt */
4141 *p++ = '\0';
4142 if (*p) WCMD_output_asis(p);
4144 /* Read the reply */
4145 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
4146 if (count > 1) {
4147 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4148 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4149 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4150 wine_dbgstr_w(string));
4151 SetEnvironmentVariableW(s, string);
4154 /* See if /A supplied, and if so calculate the results of all the expressions */
4155 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4156 NORM_IGNORECASE | SORT_STRINGSORT,
4157 s, 2, L"/A", -1) == CSTR_EQUAL) {
4158 /* /A supplied, so evaluate expressions and set variables appropriately */
4159 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4160 /* of the final computation */
4161 int result = 0;
4162 int rc = 0;
4163 WCHAR *thisexpr;
4164 WCHAR *src,*dst;
4166 /* Remove all quotes before doing any calculations */
4167 thisexpr = heap_xalloc((lstrlenW(s+2)+1) * sizeof(WCHAR));
4168 src = s+2;
4169 dst = thisexpr;
4170 while (*src) {
4171 if (*src != '"') *dst++ = *src;
4172 src++;
4174 *dst = 0;
4176 /* Now calculate the results of the expression */
4177 src = thisexpr;
4178 rc = WCMD_handleExpression(&src, &result, 0);
4179 heap_free(thisexpr);
4181 /* If parsing failed, issue the error message */
4182 if (rc > 0) {
4183 WCMD_output_stderr(WCMD_LoadMessage(rc));
4184 return;
4187 /* If we have no context (interactive or cmd.exe /c) print the final result */
4188 if (!context) {
4189 swprintf(string, ARRAY_SIZE(string), L"%d", result);
4190 WCMD_output_asis(string);
4193 } else {
4194 DWORD gle;
4196 /* set "var=value"jim ignores anything after the last quote */
4197 if (*s=='\"') {
4198 WCHAR *lastquote;
4199 lastquote = WCMD_strip_quotes(s);
4200 if (lastquote) *lastquote = 0x00;
4201 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4204 p = wcschr (s, '=');
4205 if (p == NULL) {
4206 env = GetEnvironmentStringsW();
4207 if (WCMD_setshow_sortenv( env, s ) == 0) {
4208 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4209 errorlevel = 1;
4211 return;
4213 *p++ = '\0';
4215 if (!*p) p = NULL;
4216 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4217 wine_dbgstr_w(p));
4218 status = SetEnvironmentVariableW(s, p);
4219 gle = GetLastError();
4220 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4221 errorlevel = 1;
4222 } else if (!status) WCMD_print_error();
4223 else if (!interactive) errorlevel = 0;
4227 /****************************************************************************
4228 * WCMD_setshow_path
4230 * Set/Show the path environment variable
4233 void WCMD_setshow_path (const WCHAR *args) {
4235 WCHAR string[1024];
4236 DWORD status;
4238 if (!*param1 && !*param2) {
4239 status = GetEnvironmentVariableW(L"PATH", string, ARRAY_SIZE(string));
4240 if (status != 0) {
4241 WCMD_output_asis(L"PATH=");
4242 WCMD_output_asis ( string);
4243 WCMD_output_asis(L"\r\n");
4245 else {
4246 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4249 else {
4250 if (*args == '=') args++; /* Skip leading '=' */
4251 status = SetEnvironmentVariableW(L"PATH", args);
4252 if (!status) WCMD_print_error();
4256 /****************************************************************************
4257 * WCMD_setshow_prompt
4259 * Set or show the command prompt.
4262 void WCMD_setshow_prompt (void) {
4264 WCHAR *s;
4266 if (!*param1) {
4267 SetEnvironmentVariableW(L"PROMPT", NULL);
4269 else {
4270 s = param1;
4271 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4272 if (!*s) {
4273 SetEnvironmentVariableW(L"PROMPT", NULL);
4275 else SetEnvironmentVariableW(L"PROMPT", s);
4279 /****************************************************************************
4280 * WCMD_setshow_time
4282 * Set/Show the system time
4283 * FIXME: Can't change time yet
4286 void WCMD_setshow_time (void) {
4288 WCHAR curtime[64], buffer[64];
4289 DWORD count;
4290 SYSTEMTIME st;
4292 if (!*param1) {
4293 GetLocalTime(&st);
4294 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, curtime, ARRAY_SIZE(curtime))) {
4295 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4296 if (wcsstr(quals, L"/T") == NULL) {
4297 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4298 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
4299 if (count > 2) {
4300 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4304 else WCMD_print_error ();
4306 else {
4307 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4311 /****************************************************************************
4312 * WCMD_shift
4314 * Shift batch parameters.
4315 * Optional /n says where to start shifting (n=0-8)
4318 void WCMD_shift (const WCHAR *args) {
4319 int start;
4321 if (context != NULL) {
4322 WCHAR *pos = wcschr(args, '/');
4323 int i;
4325 if (pos == NULL) {
4326 start = 0;
4327 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4328 start = (*(pos+1) - '0');
4329 } else {
4330 SetLastError(ERROR_INVALID_PARAMETER);
4331 WCMD_print_error();
4332 return;
4335 WINE_TRACE("Shifting variables, starting at %d\n", start);
4336 for (i=start;i<=8;i++) {
4337 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4339 context -> shift_count[9] = context -> shift_count[9] + 1;
4344 /****************************************************************************
4345 * WCMD_start
4347 void WCMD_start(WCHAR *args)
4349 int argno;
4350 int have_title;
4351 WCHAR file[MAX_PATH];
4352 WCHAR *cmdline, *cmdline_params;
4353 STARTUPINFOW st;
4354 PROCESS_INFORMATION pi;
4356 GetSystemDirectoryW( file, MAX_PATH );
4357 lstrcatW(file, L"\\start.exe");
4358 cmdline = heap_xalloc( (lstrlenW(file) + lstrlenW(args) + 8) * sizeof(WCHAR) );
4359 lstrcpyW( cmdline, file );
4360 lstrcatW(cmdline, L" ");
4361 cmdline_params = cmdline + lstrlenW(cmdline);
4363 /* The start built-in has some special command-line parsing properties
4364 * which will be outlined here.
4366 * both '\t' and ' ' are argument separators
4367 * '/' has a special double role as both separator and switch prefix, e.g.
4369 * > start /low/i
4370 * or
4371 * > start "title"/i
4373 * are valid ways to pass multiple options to start. In the latter case
4374 * '/i' is not a part of the title but parsed as a switch.
4376 * However, '=', ';' and ',' are not separators:
4377 * > start "deus"=ex,machina
4379 * will in fact open a console titled 'deus=ex,machina'
4381 * The title argument parsing code is only interested in quotes themselves,
4382 * it does not respect escaping of any kind and all quotes are dropped
4383 * from the resulting title, therefore:
4385 * > start "\"" hello"/low
4387 * actually opens a console titled '\ hello' with low priorities.
4389 * To not break compatibility with wine programs relying on
4390 * wine's separate 'start.exe', this program's peculiar console
4391 * title parsing is actually implemented in 'cmd.exe' which is the
4392 * application native Windows programs will use to invoke 'start'.
4394 * WCMD_parameter_with_delims will take care of everything for us.
4396 have_title = FALSE;
4397 for (argno=0; ; argno++) {
4398 WCHAR *thisArg, *argN;
4400 argN = NULL;
4401 thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, L" \t/");
4403 /* No more parameters */
4404 if (!argN)
4405 break;
4407 /* Found the title */
4408 if (argN[0] == '"') {
4409 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
4410 have_title = TRUE;
4412 /* Copy all of the cmdline processed */
4413 memcpy(cmdline_params, args, sizeof(WCHAR) * (argN - args));
4414 cmdline_params[argN - args] = '\0';
4416 /* Add quoted title */
4417 lstrcatW(cmdline_params, L"\"\\\"");
4418 lstrcatW(cmdline_params, thisArg);
4419 lstrcatW(cmdline_params, L"\\\"\"");
4421 /* Concatenate remaining command-line */
4422 thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, L" \t/");
4423 lstrcatW(cmdline_params, argN + lstrlenW(thisArg));
4425 break;
4428 /* Skipping a regular argument? */
4429 else if (argN != args && argN[-1] == '/') {
4430 continue;
4432 /* Not an argument nor the title, start of program arguments,
4433 * stop looking for title.
4435 } else
4436 break;
4439 /* build command-line if not built yet */
4440 if (!have_title) {
4441 lstrcatW( cmdline, args );
4444 memset( &st, 0, sizeof(STARTUPINFOW) );
4445 st.cb = sizeof(STARTUPINFOW);
4447 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4449 WaitForSingleObject( pi.hProcess, INFINITE );
4450 GetExitCodeProcess( pi.hProcess, &errorlevel );
4451 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4452 CloseHandle(pi.hProcess);
4453 CloseHandle(pi.hThread);
4455 else
4457 SetLastError(ERROR_FILE_NOT_FOUND);
4458 WCMD_print_error ();
4459 errorlevel = 9009;
4461 heap_free(cmdline);
4464 /****************************************************************************
4465 * WCMD_title
4467 * Set the console title
4469 void WCMD_title (const WCHAR *args) {
4470 SetConsoleTitleW(args);
4473 /****************************************************************************
4474 * WCMD_type
4476 * Copy a file to standard output.
4479 void WCMD_type (WCHAR *args) {
4481 int argno = 0;
4482 WCHAR *argN = args;
4483 BOOL writeHeaders = FALSE;
4485 if (param1[0] == 0x00) {
4486 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4487 return;
4490 if (param2[0] != 0x00) writeHeaders = TRUE;
4492 /* Loop through all args */
4493 errorlevel = 0;
4494 while (argN) {
4495 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4497 HANDLE h;
4498 WCHAR buffer[512];
4499 DWORD count;
4501 if (!argN) break;
4503 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4504 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4505 FILE_ATTRIBUTE_NORMAL, NULL);
4506 if (h == INVALID_HANDLE_VALUE) {
4507 WCMD_print_error ();
4508 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4509 errorlevel = 1;
4510 } else {
4511 if (writeHeaders) {
4512 WCMD_output_stderr(L"\n%1\n\n\n", thisArg);
4514 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer) - 1, &count)) {
4515 if (count == 0) break; /* ReadFile reports success on EOF! */
4516 buffer[count] = 0;
4517 WCMD_output_asis (buffer);
4519 CloseHandle (h);
4524 /****************************************************************************
4525 * WCMD_more
4527 * Output either a file or stdin to screen in pages
4530 void WCMD_more (WCHAR *args) {
4532 int argno = 0;
4533 WCHAR *argN = args;
4534 WCHAR moreStr[100];
4535 WCHAR moreStrPage[100];
4536 WCHAR buffer[512];
4537 DWORD count;
4539 /* Prefix the NLS more with '-- ', then load the text */
4540 errorlevel = 0;
4541 lstrcpyW(moreStr, L"-- ");
4542 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3], ARRAY_SIZE(moreStr)-3);
4544 if (param1[0] == 0x00) {
4546 /* Wine implements pipes via temporary files, and hence stdin is
4547 effectively reading from the file. This means the prompts for
4548 more are satisfied by the next line from the input (file). To
4549 avoid this, ensure stdin is to the console */
4550 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4551 HANDLE hConIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
4552 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4553 FILE_ATTRIBUTE_NORMAL, 0);
4554 WINE_TRACE("No parms - working probably in pipe mode\n");
4555 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4557 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4558 once you get in this bit unless due to a pipe, it's going to end badly... */
4559 wsprintfW(moreStrPage, L"%s --\n", moreStr);
4561 WCMD_enter_paged_mode(moreStrPage);
4562 while (WCMD_ReadFile(hstdin, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4563 if (count == 0) break; /* ReadFile reports success on EOF! */
4564 buffer[count] = 0;
4565 WCMD_output_asis (buffer);
4567 WCMD_leave_paged_mode();
4569 /* Restore stdin to what it was */
4570 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4571 CloseHandle(hConIn);
4573 return;
4574 } else {
4575 BOOL needsPause = FALSE;
4577 /* Loop through all args */
4578 WINE_TRACE("Parms supplied - working through each file\n");
4579 WCMD_enter_paged_mode(moreStrPage);
4581 while (argN) {
4582 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4583 HANDLE h;
4585 if (!argN) break;
4587 if (needsPause) {
4589 /* Wait */
4590 wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, 100);
4591 WCMD_leave_paged_mode();
4592 WCMD_output_asis(moreStrPage);
4593 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
4594 WCMD_enter_paged_mode(moreStrPage);
4598 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4599 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4600 FILE_ATTRIBUTE_NORMAL, NULL);
4601 if (h == INVALID_HANDLE_VALUE) {
4602 WCMD_print_error ();
4603 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4604 errorlevel = 1;
4605 } else {
4606 ULONG64 curPos = 0;
4607 ULONG64 fileLen = 0;
4608 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4610 /* Get the file size */
4611 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4612 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4614 needsPause = TRUE;
4615 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4616 if (count == 0) break; /* ReadFile reports success on EOF! */
4617 buffer[count] = 0;
4618 curPos += count;
4620 /* Update % count (would be used in WCMD_output_asis as prompt) */
4621 wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, (int) min(99, (curPos * 100)/fileLen));
4623 WCMD_output_asis (buffer);
4625 CloseHandle (h);
4629 WCMD_leave_paged_mode();
4633 /****************************************************************************
4634 * WCMD_verify
4636 * Display verify flag.
4637 * FIXME: We don't actually do anything with the verify flag other than toggle
4638 * it...
4641 void WCMD_verify (const WCHAR *args) {
4643 int count;
4645 count = lstrlenW(args);
4646 if (count == 0) {
4647 if (verify_mode) WCMD_output(WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"ON");
4648 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"OFF");
4649 return;
4651 if (lstrcmpiW(args, L"ON") == 0) {
4652 verify_mode = TRUE;
4653 return;
4655 else if (lstrcmpiW(args, L"OFF") == 0) {
4656 verify_mode = FALSE;
4657 return;
4659 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4662 /****************************************************************************
4663 * WCMD_version
4665 * Display version info.
4668 void WCMD_version (void) {
4670 WCMD_output_asis (version_string);
4674 /****************************************************************************
4675 * WCMD_volume
4677 * Display volume information (set_label = FALSE)
4678 * Additionally set volume label (set_label = TRUE)
4679 * Returns 1 on success, 0 otherwise
4682 int WCMD_volume(BOOL set_label, const WCHAR *path)
4684 DWORD count, serial;
4685 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4686 BOOL status;
4688 if (!*path) {
4689 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
4690 if (!status) {
4691 WCMD_print_error ();
4692 return 0;
4694 status = GetVolumeInformationW(NULL, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4696 else {
4697 if ((path[1] != ':') || (lstrlenW(path) != 2)) {
4698 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4699 return 0;
4701 wsprintfW (curdir, L"%s\\", path);
4702 status = GetVolumeInformationW(curdir, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4704 if (!status) {
4705 WCMD_print_error ();
4706 return 0;
4708 if (label[0] != '\0') {
4709 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4710 curdir[0], label);
4712 else {
4713 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4714 curdir[0]);
4716 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4717 HIWORD(serial), LOWORD(serial));
4718 if (set_label) {
4719 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4720 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
4721 if (count > 1) {
4722 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4723 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4725 if (*path) {
4726 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4728 else {
4729 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4732 return 1;
4735 /**************************************************************************
4736 * WCMD_exit
4738 * Exit either the process, or just this batch program
4742 void WCMD_exit (CMD_LIST **cmdList) {
4743 int rc = wcstol(param1, NULL, 10); /* Note: wcstol of empty parameter is 0 */
4745 if (context && lstrcmpiW(quals, L"/B") == 0) {
4746 errorlevel = rc;
4747 context -> skip_rest = TRUE;
4748 *cmdList = NULL;
4749 } else {
4750 ExitProcess(rc);
4755 /*****************************************************************************
4756 * WCMD_assoc
4758 * Lists or sets file associations (assoc = TRUE)
4759 * Lists or sets file types (assoc = FALSE)
4761 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4763 HKEY key;
4764 DWORD accessOptions = KEY_READ;
4765 WCHAR *newValue;
4766 LONG rc = ERROR_SUCCESS;
4767 WCHAR keyValue[MAXSTRING];
4768 DWORD valueLen;
4769 HKEY readKey;
4771 /* See if parameter includes '=' */
4772 errorlevel = 0;
4773 newValue = wcschr(args, '=');
4774 if (newValue) accessOptions |= KEY_WRITE;
4776 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4777 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, L"", 0, accessOptions, &key) != ERROR_SUCCESS) {
4778 WINE_FIXME("Unexpected failure opening HKCR key: %ld\n", GetLastError());
4779 return;
4782 /* If no parameters then list all associations */
4783 if (*args == 0x00) {
4784 int index = 0;
4786 /* Enumerate all the keys */
4787 while (rc != ERROR_NO_MORE_ITEMS) {
4788 WCHAR keyName[MAXSTRING];
4789 DWORD nameLen;
4791 /* Find the next value */
4792 nameLen = MAXSTRING;
4793 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4795 if (rc == ERROR_SUCCESS) {
4797 /* Only interested in extension ones if assoc, or others
4798 if not assoc */
4799 if ((keyName[0] == '.' && assoc) ||
4800 (!(keyName[0] == '.') && (!assoc)))
4802 WCHAR subkey[MAXSTRING];
4803 lstrcpyW(subkey, keyName);
4804 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4806 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4808 valueLen = sizeof(keyValue);
4809 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4810 WCMD_output_asis(keyName);
4811 WCMD_output_asis(L"=");
4812 /* If no default value found, leave line empty after '=' */
4813 if (rc == ERROR_SUCCESS) {
4814 WCMD_output_asis(keyValue);
4816 WCMD_output_asis(L"\r\n");
4817 RegCloseKey(readKey);
4823 } else {
4825 /* Parameter supplied - if no '=' on command line, it's a query */
4826 if (newValue == NULL) {
4827 WCHAR *space;
4828 WCHAR subkey[MAXSTRING];
4830 /* Query terminates the parameter at the first space */
4831 lstrcpyW(keyValue, args);
4832 space = wcschr(keyValue, ' ');
4833 if (space) *space=0x00;
4835 /* Set up key name */
4836 lstrcpyW(subkey, keyValue);
4837 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4839 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4841 valueLen = sizeof(keyValue);
4842 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4843 WCMD_output_asis(args);
4844 WCMD_output_asis(L"=");
4845 /* If no default value found, leave line empty after '=' */
4846 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4847 WCMD_output_asis(L"\r\n");
4848 RegCloseKey(readKey);
4850 } else {
4851 WCHAR msgbuffer[MAXSTRING];
4853 /* Load the translated 'File association not found' */
4854 if (assoc) {
4855 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4856 } else {
4857 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4859 WCMD_output_stderr(msgbuffer, keyValue);
4860 errorlevel = 2;
4863 /* Not a query - it's a set or clear of a value */
4864 } else {
4866 WCHAR subkey[MAXSTRING];
4868 /* Get pointer to new value */
4869 *newValue = 0x00;
4870 newValue++;
4872 /* Set up key name */
4873 lstrcpyW(subkey, args);
4874 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4876 /* If nothing after '=' then clear value - only valid for ASSOC */
4877 if (*newValue == 0x00) {
4879 if (assoc) rc = RegDeleteKeyW(key, args);
4880 if (assoc && rc == ERROR_SUCCESS) {
4881 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4883 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4884 WCMD_print_error();
4885 errorlevel = 2;
4887 } else {
4888 WCHAR msgbuffer[MAXSTRING];
4890 /* Load the translated 'File association not found' */
4891 if (assoc) {
4892 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4893 } else {
4894 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4896 WCMD_output_stderr(msgbuffer, args);
4897 errorlevel = 2;
4900 /* It really is a set value = contents */
4901 } else {
4902 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4903 accessOptions, NULL, &readKey, NULL);
4904 if (rc == ERROR_SUCCESS) {
4905 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4906 (LPBYTE)newValue,
4907 sizeof(WCHAR) * (lstrlenW(newValue) + 1));
4908 RegCloseKey(readKey);
4911 if (rc != ERROR_SUCCESS) {
4912 WCMD_print_error();
4913 errorlevel = 2;
4914 } else {
4915 WCMD_output_asis(args);
4916 WCMD_output_asis(L"=");
4917 WCMD_output_asis(newValue);
4918 WCMD_output_asis(L"\r\n");
4924 /* Clean up */
4925 RegCloseKey(key);
4928 /****************************************************************************
4929 * WCMD_color
4931 * Colors the terminal screen.
4934 void WCMD_color (void) {
4936 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4937 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4939 if (param1[0] != 0x00 && lstrlenW(param1) > 2) {
4940 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4941 return;
4944 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4946 COORD topLeft;
4947 DWORD screenSize;
4948 DWORD color = 0;
4950 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4952 topLeft.X = 0;
4953 topLeft.Y = 0;
4955 /* Convert the color hex digits */
4956 if (param1[0] == 0x00) {
4957 color = defaultColor;
4958 } else {
4959 color = wcstoul(param1, NULL, 16);
4962 /* Fail if fg == bg color */
4963 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4964 errorlevel = 1;
4965 return;
4968 /* Set the current screen contents and ensure all future writes
4969 remain this color */
4970 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4971 SetConsoleTextAttribute(hStdOut, color);
4975 /****************************************************************************
4976 * WCMD_mklink
4979 void WCMD_mklink(WCHAR *args)
4981 int argno = 0;
4982 WCHAR *argN = args;
4983 BOOL isdir = FALSE;
4984 BOOL junction = FALSE;
4985 BOOL hard = FALSE;
4986 BOOL ret = FALSE;
4987 WCHAR file1[MAX_PATH];
4988 WCHAR file2[MAX_PATH];
4990 if (param1[0] == 0x00 || param2[0] == 0x00) {
4991 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4992 return;
4995 file1[0] = 0;
4997 while (argN) {
4998 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
5000 if (!argN) break;
5002 WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
5004 if (lstrcmpiW(thisArg, L"/D") == 0)
5005 isdir = TRUE;
5006 else if (lstrcmpiW(thisArg, L"/H") == 0)
5007 hard = TRUE;
5008 else if (lstrcmpiW(thisArg, L"/J") == 0)
5009 junction = TRUE;
5010 else {
5011 if(!file1[0])
5012 lstrcpyW(file1, thisArg);
5013 else
5014 lstrcpyW(file2, thisArg);
5018 if(hard)
5019 ret = CreateHardLinkW(file1, file2, NULL);
5020 else if(!junction)
5021 ret = CreateSymbolicLinkW(file1, file2, isdir);
5022 else
5023 WINE_TRACE("Juction links currently not supported.\n");
5025 if(!ret)
5026 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), file1);