strmbase: Move run_event to the strmbase_renderer structure.
[wine.git] / programs / cmd / builtins.c
blob0fb40d94e49f2663c6e7328c66d751b3f6f551c1
1 /*
2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * FIXME:
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
31 #include "wcmd.h"
32 #include <shellapi.h>
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
37 extern int defaultColor;
38 extern BOOL echo_mode;
39 extern BOOL interactive;
41 struct env_stack *pushd_directories;
42 const WCHAR inbuilt[][10] = {
43 L"CALL",
44 L"CD",
45 L"CHDIR",
46 L"CLS",
47 L"COPY",
48 L"CTTY",
49 L"DATE",
50 L"DEL",
51 L"DIR",
52 L"ECHO",
53 L"ERASE",
54 L"FOR",
55 L"GOTO",
56 L"HELP",
57 L"IF",
58 L"LABEL",
59 L"MD",
60 L"MKDIR",
61 L"MOVE",
62 L"PATH",
63 L"PAUSE",
64 L"PROMPT",
65 L"REM",
66 L"REN",
67 L"RENAME",
68 L"RD",
69 L"RMDIR",
70 L"SET",
71 L"SHIFT",
72 L"START",
73 L"TIME",
74 L"TITLE",
75 L"TYPE",
76 L"VERIFY",
77 L"VER",
78 L"VOL",
79 L"ENDLOCAL",
80 L"SETLOCAL",
81 L"PUSHD",
82 L"POPD",
83 L"ASSOC",
84 L"COLOR",
85 L"FTYPE",
86 L"MORE",
87 L"CHOICE",
88 L"MKLINK",
89 L"EXIT"
91 static const WCHAR externals[][10] = {
92 L"ATTRIB",
93 L"XCOPY"
96 static HINSTANCE hinst;
97 struct env_stack *saved_environment;
98 static BOOL verify_mode = FALSE;
100 /* set /a routines work from single character operators, but some of the
101 operators are multiple character ones, especially the assignment ones.
102 Temporarily represent these using the values below on the operator stack */
103 #define OP_POSITIVE 'P'
104 #define OP_NEGATIVE 'N'
105 #define OP_ASSSIGNMUL 'a'
106 #define OP_ASSSIGNDIV 'b'
107 #define OP_ASSSIGNMOD 'c'
108 #define OP_ASSSIGNADD 'd'
109 #define OP_ASSSIGNSUB 'e'
110 #define OP_ASSSIGNAND 'f'
111 #define OP_ASSSIGNNOT 'g'
112 #define OP_ASSSIGNOR 'h'
113 #define OP_ASSSIGNSHL 'i'
114 #define OP_ASSSIGNSHR 'j'
116 /* This maintains a stack of operators, holding both the operator precedence
117 and the single character representation of the operator in question */
118 typedef struct _OPSTACK
120 int precedence;
121 WCHAR op;
122 struct _OPSTACK *next;
123 } OPSTACK;
125 /* This maintains a stack of values, where each value can either be a
126 numeric value, or a string representing an environment variable */
127 typedef struct _VARSTACK
129 BOOL isnum;
130 WCHAR *variable;
131 int value;
132 struct _VARSTACK *next;
133 } VARSTACK;
135 /* This maintains a mapping between the calculated operator and the
136 single character representation for the assignment operators. */
137 static struct
139 WCHAR op;
140 WCHAR calculatedop;
141 } calcassignments[] =
143 {'*', OP_ASSSIGNMUL},
144 {'/', OP_ASSSIGNDIV},
145 {'%', OP_ASSSIGNMOD},
146 {'+', OP_ASSSIGNADD},
147 {'-', OP_ASSSIGNSUB},
148 {'&', OP_ASSSIGNAND},
149 {'^', OP_ASSSIGNNOT},
150 {'|', OP_ASSSIGNOR},
151 {'<', OP_ASSSIGNSHL},
152 {'>', OP_ASSSIGNSHR},
153 {' ',' '}
156 /**************************************************************************
157 * WCMD_ask_confirm
159 * Issue a message and ask for confirmation, waiting on a valid answer.
161 * Returns True if Y (or A) answer is selected
162 * If optionAll contains a pointer, ALL is allowed, and if answered
163 * set to TRUE
166 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
167 BOOL *optionAll) {
169 UINT msgid;
170 WCHAR confirm[MAXSTRING];
171 WCHAR options[MAXSTRING];
172 WCHAR Ybuffer[MAXSTRING];
173 WCHAR Nbuffer[MAXSTRING];
174 WCHAR Abuffer[MAXSTRING];
175 WCHAR answer[MAX_PATH] = {'\0'};
176 DWORD count = 0;
178 /* Load the translated valid answers */
179 if (showSureText)
180 LoadStringW(hinst, WCMD_CONFIRM, confirm, ARRAY_SIZE(confirm));
181 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
182 LoadStringW(hinst, msgid, options, ARRAY_SIZE(options));
183 LoadStringW(hinst, WCMD_YES, Ybuffer, ARRAY_SIZE(Ybuffer));
184 LoadStringW(hinst, WCMD_NO, Nbuffer, ARRAY_SIZE(Nbuffer));
185 LoadStringW(hinst, WCMD_ALL, Abuffer, ARRAY_SIZE(Abuffer));
187 /* Loop waiting on a valid answer */
188 if (optionAll)
189 *optionAll = FALSE;
190 while (1)
192 WCMD_output_asis (message);
193 if (showSureText)
194 WCMD_output_asis (confirm);
195 WCMD_output_asis (options);
196 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, ARRAY_SIZE(answer), &count);
197 answer[0] = towupper(answer[0]);
198 if (answer[0] == Ybuffer[0])
199 return TRUE;
200 if (answer[0] == Nbuffer[0])
201 return FALSE;
202 if (optionAll && answer[0] == Abuffer[0])
204 *optionAll = TRUE;
205 return TRUE;
210 /****************************************************************************
211 * WCMD_clear_screen
213 * Clear the terminal screen.
216 void WCMD_clear_screen (void) {
218 /* Emulate by filling the screen from the top left to bottom right with
219 spaces, then moving the cursor to the top left afterwards */
220 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
221 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
223 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
225 COORD topLeft;
226 DWORD screenSize, written;
228 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
230 topLeft.X = 0;
231 topLeft.Y = 0;
232 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
233 FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
234 SetConsoleCursorPosition(hStdOut, topLeft);
238 /****************************************************************************
239 * WCMD_change_tty
241 * Change the default i/o device (ie redirect STDin/STDout).
244 void WCMD_change_tty (void) {
246 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
250 /****************************************************************************
251 * WCMD_choice
255 void WCMD_choice (const WCHAR * args) {
256 WCHAR answer[16];
257 WCHAR buffer[16];
258 WCHAR *ptr = NULL;
259 WCHAR *opt_c = NULL;
260 WCHAR *my_command = NULL;
261 WCHAR opt_default = 0;
262 DWORD opt_timeout = 0;
263 DWORD count;
264 DWORD oldmode;
265 BOOL have_console;
266 BOOL opt_n = FALSE;
267 BOOL opt_s = FALSE;
269 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
270 errorlevel = 0;
272 my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
274 ptr = WCMD_skip_leading_spaces(my_command);
275 while (*ptr == '/') {
276 switch (towupper(ptr[1])) {
277 case 'C':
278 ptr += 2;
279 /* the colon is optional */
280 if (*ptr == ':')
281 ptr++;
283 if (!*ptr || iswspace(*ptr)) {
284 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
285 heap_free(my_command);
286 return;
289 /* remember the allowed keys (overwrite previous /C option) */
290 opt_c = ptr;
291 while (*ptr && (!iswspace(*ptr)))
292 ptr++;
294 if (*ptr) {
295 /* terminate allowed chars */
296 *ptr = 0;
297 ptr = WCMD_skip_leading_spaces(&ptr[1]);
299 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
300 break;
302 case 'N':
303 opt_n = TRUE;
304 ptr = WCMD_skip_leading_spaces(&ptr[2]);
305 break;
307 case 'S':
308 opt_s = TRUE;
309 ptr = WCMD_skip_leading_spaces(&ptr[2]);
310 break;
312 case 'T':
313 ptr = &ptr[2];
314 /* the colon is optional */
315 if (*ptr == ':')
316 ptr++;
318 opt_default = *ptr++;
320 if (!opt_default || (*ptr != ',')) {
321 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
322 heap_free(my_command);
323 return;
325 ptr++;
327 count = 0;
328 while (((answer[count] = *ptr)) && iswdigit(*ptr) && (count < 15)) {
329 count++;
330 ptr++;
333 answer[count] = 0;
334 opt_timeout = wcstol(answer, NULL, 10);
336 ptr = WCMD_skip_leading_spaces(ptr);
337 break;
339 default:
340 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
341 heap_free(my_command);
342 return;
346 if (opt_timeout)
347 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
349 if (have_console)
350 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
352 /* use default keys, when needed: localized versions of "Y"es and "No" */
353 if (!opt_c) {
354 LoadStringW(hinst, WCMD_YES, buffer, ARRAY_SIZE(buffer));
355 LoadStringW(hinst, WCMD_NO, buffer + 1, ARRAY_SIZE(buffer) - 1);
356 opt_c = buffer;
357 buffer[2] = 0;
360 /* print the question, when needed */
361 if (*ptr)
362 WCMD_output_asis(ptr);
364 if (!opt_s) {
365 wcsupr(opt_c);
366 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
369 if (!opt_n) {
370 /* print a list of all allowed answers inside brackets */
371 WCMD_output_asis(L"[");
372 ptr = opt_c;
373 answer[1] = 0;
374 while ((answer[0] = *ptr++)) {
375 WCMD_output_asis(answer);
376 if (*ptr)
377 WCMD_output_asis(L",");
379 WCMD_output_asis(L"]?");
382 while (TRUE) {
384 /* FIXME: Add support for option /T */
385 answer[1] = 0; /* terminate single character string */
386 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
388 if (!opt_s)
389 answer[0] = towupper(answer[0]);
391 ptr = wcschr(opt_c, answer[0]);
392 if (ptr) {
393 WCMD_output_asis(answer);
394 WCMD_output_asis(L"\r\n");
395 if (have_console)
396 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
398 errorlevel = (ptr - opt_c) + 1;
399 WINE_TRACE("answer: %d\n", errorlevel);
400 heap_free(my_command);
401 return;
403 else
405 /* key not allowed: play the bell */
406 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
407 WCMD_output_asis(L"\a");
412 /****************************************************************************
413 * WCMD_AppendEOF
415 * Adds an EOF onto the end of a file
416 * Returns TRUE on success
418 static BOOL WCMD_AppendEOF(WCHAR *filename)
420 HANDLE h;
421 DWORD bytes_written;
423 char eof = '\x1a';
425 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
426 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
427 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
429 if (h == INVALID_HANDLE_VALUE) {
430 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
431 return FALSE;
432 } else {
433 SetFilePointer (h, 0, NULL, FILE_END);
434 if (!WriteFile(h, &eof, 1, &bytes_written, NULL)) {
435 WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
436 CloseHandle(h);
437 return FALSE;
439 CloseHandle(h);
441 return TRUE;
444 /****************************************************************************
445 * WCMD_IsSameFile
447 * Checks if the two paths reference to the same file
449 static BOOL WCMD_IsSameFile(const WCHAR *name1, const WCHAR *name2)
451 BOOL ret = FALSE;
452 HANDLE file1 = INVALID_HANDLE_VALUE, file2 = INVALID_HANDLE_VALUE;
453 BY_HANDLE_FILE_INFORMATION info1, info2;
455 file1 = CreateFileW(name1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
456 if (file1 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file1, &info1))
457 goto end;
459 file2 = CreateFileW(name2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
460 if (file2 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file2, &info2))
461 goto end;
463 ret = info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
464 && info1.nFileIndexHigh == info2.nFileIndexHigh
465 && info1.nFileIndexLow == info2.nFileIndexLow;
466 end:
467 if (file1 != INVALID_HANDLE_VALUE)
468 CloseHandle(file1);
469 if (file2 != INVALID_HANDLE_VALUE)
470 CloseHandle(file2);
471 return ret;
474 /****************************************************************************
475 * WCMD_ManualCopy
477 * Copies from a file
478 * optionally reading only until EOF (ascii copy)
479 * optionally appending onto an existing file (append)
480 * Returns TRUE on success
482 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
484 HANDLE in,out;
485 BOOL ok;
486 DWORD bytesread, byteswritten;
488 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
489 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
491 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
492 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
493 if (in == INVALID_HANDLE_VALUE) {
494 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
495 return FALSE;
498 /* Open the output file, overwriting if not appending */
499 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
500 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
501 if (out == INVALID_HANDLE_VALUE) {
502 WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
503 CloseHandle(in);
504 return FALSE;
507 /* Move to end of destination if we are going to append to it */
508 if (append) {
509 SetFilePointer(out, 0, NULL, FILE_END);
512 /* Loop copying data from source to destination until EOF read */
515 char buffer[MAXSTRING];
517 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
518 if (ok) {
520 /* Stop at first EOF */
521 if (ascii) {
522 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
523 if (ptr) bytesread = (ptr - buffer);
526 if (bytesread) {
527 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
528 if (!ok || byteswritten != bytesread) {
529 WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
530 wine_dbgstr_w(dstname), GetLastError());
533 } else {
534 WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
535 wine_dbgstr_w(srcname), GetLastError());
537 } while (ok && bytesread > 0);
539 CloseHandle(out);
540 CloseHandle(in);
541 return ok;
544 /****************************************************************************
545 * WCMD_copy
547 * Copy a file or wildcarded set.
548 * For ascii/binary type copies, it gets complex:
549 * Syntax on command line is
550 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
551 * Where first /a or /b sets 'mode in operation' until another is found
552 * once another is found, it applies to the file preceding the /a or /b
553 * In addition each filename can contain wildcards
554 * To make matters worse, the + may be in the same parameter (i.e. no
555 * whitespace) or with whitespace separating it
557 * ASCII mode on read == read and stop at first EOF
558 * ASCII mode on write == append EOF to destination
559 * Binary == copy as-is
561 * Design of this is to build up a list of files which will be copied into a
562 * list, then work through the list file by file.
563 * If no destination is specified, it defaults to the name of the first file in
564 * the list, but the current directory.
568 void WCMD_copy(WCHAR * args) {
570 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
571 WCHAR *thisparam;
572 int argno = 0;
573 WCHAR *rawarg;
574 WIN32_FIND_DATAW fd;
575 HANDLE hff = INVALID_HANDLE_VALUE;
576 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
577 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
578 BOOL anyconcats = FALSE; /* Have we found any + options */
579 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
580 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
581 BOOL prompt; /* Prompt before overwriting */
582 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
583 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
584 BOOL status;
585 WCHAR copycmd[4];
586 DWORD len;
587 BOOL dstisdevice = FALSE;
589 typedef struct _COPY_FILES
591 struct _COPY_FILES *next;
592 BOOL concatenate;
593 WCHAR *name;
594 int binarycopy;
595 } COPY_FILES;
596 COPY_FILES *sourcelist = NULL;
597 COPY_FILES *lastcopyentry = NULL;
598 COPY_FILES *destination = NULL;
599 COPY_FILES *thiscopy = NULL;
600 COPY_FILES *prevcopy = NULL;
602 /* Assume we were successful! */
603 errorlevel = 0;
605 /* If no args supplied at all, report an error */
606 if (param1[0] == 0x00) {
607 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
608 errorlevel = 1;
609 return;
612 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
614 /* Walk through all args, building up a list of files to process */
615 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
616 while (*(thisparam)) {
617 WCHAR *pos1, *pos2;
618 BOOL inquotes;
620 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
622 /* Handle switches */
623 if (*thisparam == '/') {
624 while (*thisparam == '/') {
625 thisparam++;
626 if (towupper(*thisparam) == 'D') {
627 opt_d = TRUE;
628 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
629 } else if (towupper(*thisparam) == 'Y') {
630 opt_y = TRUE;
631 } else if (towupper(*thisparam) == '-' && towupper(*(thisparam+1)) == 'Y') {
632 opt_noty = TRUE;
633 } else if (towupper(*thisparam) == 'V') {
634 opt_v = TRUE;
635 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
636 } else if (towupper(*thisparam) == 'N') {
637 opt_n = TRUE;
638 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
639 } else if (towupper(*thisparam) == 'Z') {
640 opt_z = TRUE;
641 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
642 } else if (towupper(*thisparam) == 'A') {
643 if (binarymode != 0) {
644 binarymode = 0;
645 WINE_TRACE("Subsequent files will be handled as ASCII\n");
646 if (destination != NULL) {
647 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
648 destination->binarycopy = binarymode;
649 } else if (lastcopyentry != NULL) {
650 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
651 lastcopyentry->binarycopy = binarymode;
654 } else if (towupper(*thisparam) == 'B') {
655 if (binarymode != 1) {
656 binarymode = 1;
657 WINE_TRACE("Subsequent files will be handled as binary\n");
658 if (destination != NULL) {
659 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
660 destination->binarycopy = binarymode;
661 } else if (lastcopyentry != NULL) {
662 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
663 lastcopyentry->binarycopy = binarymode;
666 } else {
667 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
669 thisparam++;
672 /* This parameter was purely switches, get the next one */
673 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
674 continue;
677 /* We have found something which is not a switch. If could be anything of the form
678 sourcefilename (which could be destination too)
679 + (when filename + filename syntex used)
680 sourcefilename+sourcefilename
681 +sourcefilename
682 +/b[tests show windows then ignores to end of parameter]
685 if (*thisparam=='+') {
686 if (lastcopyentry == NULL) {
687 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
688 errorlevel = 1;
689 goto exitreturn;
690 } else {
691 concatnextfilename = TRUE;
692 anyconcats = TRUE;
695 /* Move to next thing to process */
696 thisparam++;
697 if (*thisparam == 0x00)
698 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
699 continue;
702 /* We have found something to process - build a COPY_FILE block to store it */
703 thiscopy = heap_xalloc(sizeof(COPY_FILES));
705 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
706 thiscopy->concatenate = concatnextfilename;
707 thiscopy->binarycopy = binarymode;
708 thiscopy->next = NULL;
710 /* Time to work out the name. Allocate at least enough space (deliberately too much to
711 leave space to append \* to the end) , then copy in character by character. Strip off
712 quotes if we find them. */
713 len = lstrlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
714 thiscopy->name = heap_xalloc(len*sizeof(WCHAR));
715 memset(thiscopy->name, 0x00, len);
717 pos1 = thisparam;
718 pos2 = thiscopy->name;
719 inquotes = FALSE;
720 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
721 if (*pos1 == '"') {
722 inquotes = !inquotes;
723 pos1++;
724 } else *pos2++ = *pos1++;
726 *pos2 = 0;
727 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
729 /* This is either the first source, concatenated subsequent source or destination */
730 if (sourcelist == NULL) {
731 WINE_TRACE("Adding as first source part\n");
732 sourcelist = thiscopy;
733 lastcopyentry = thiscopy;
734 } else if (concatnextfilename) {
735 WINE_TRACE("Adding to source file list to be concatenated\n");
736 lastcopyentry->next = thiscopy;
737 lastcopyentry = thiscopy;
738 } else if (destination == NULL) {
739 destination = thiscopy;
740 } else {
741 /* We have processed sources and destinations and still found more to do - invalid */
742 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
743 errorlevel = 1;
744 goto exitreturn;
746 concatnextfilename = FALSE;
748 /* We either need to process the rest of the parameter or move to the next */
749 if (*pos1 == '/' || *pos1 == '+') {
750 thisparam = pos1;
751 continue;
752 } else {
753 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
757 /* Ensure we have at least one source file */
758 if (!sourcelist) {
759 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
760 errorlevel = 1;
761 goto exitreturn;
764 /* Default whether automatic overwriting is on. If we are interactive then
765 we prompt by default, otherwise we overwrite by default
766 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
767 if (opt_noty) prompt = TRUE;
768 else if (opt_y) prompt = FALSE;
769 else {
770 /* By default, we will force the overwrite in batch mode and ask for
771 * confirmation in interactive mode. */
772 prompt = interactive;
773 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
774 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
775 * default behavior. */
776 len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
777 if (len && len < ARRAY_SIZE(copycmd)) {
778 if (!lstrcmpiW(copycmd, L"/Y"))
779 prompt = FALSE;
780 else if (!lstrcmpiW(copycmd, L"/-Y"))
781 prompt = TRUE;
785 /* Calculate the destination now - if none supplied, it's current dir +
786 filename of first file in list*/
787 if (destination == NULL) {
789 WINE_TRACE("No destination supplied, so need to calculate it\n");
790 lstrcpyW(destname, L".");
791 lstrcatW(destname, L"\\");
793 destination = heap_xalloc(sizeof(COPY_FILES));
794 if (destination == NULL) goto exitreturn;
795 destination->concatenate = FALSE; /* Not used for destination */
796 destination->binarycopy = binarymode;
797 destination->next = NULL; /* Not used for destination */
798 destination->name = NULL; /* To be filled in */
799 destisdirectory = TRUE;
801 } else {
802 WCHAR *filenamepart;
803 DWORD attributes;
805 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
807 /* Convert to fully qualified path/filename */
808 GetFullPathNameW(destination->name, ARRAY_SIZE(destname), destname, &filenamepart);
809 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
811 /* If parameter is a directory, ensure it ends in \ */
812 attributes = GetFileAttributesW(destname);
813 if (ends_with_backslash( destname ) ||
814 ((attributes != INVALID_FILE_ATTRIBUTES) &&
815 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
817 destisdirectory = TRUE;
818 if (!ends_with_backslash(destname)) lstrcatW(destname, L"\\");
819 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
823 /* Normally, the destination is the current directory unless we are
824 concatenating, in which case it's current directory plus first filename.
825 Note that if the
826 In addition by default it is a binary copy unless concatenating, when
827 the copy defaults to an ascii copy (stop at EOF). We do not know the
828 first source part yet (until we search) so flag as needing filling in. */
830 if (anyconcats) {
831 /* We have found an a+b type syntax, so destination has to be a filename
832 and we need to default to ascii copying. If we have been supplied a
833 directory as the destination, we need to defer calculating the name */
834 if (destisdirectory) appendfirstsource = TRUE;
835 if (destination->binarycopy == -1) destination->binarycopy = 0;
837 } else if (!destisdirectory) {
838 /* We have been asked to copy to a filename. Default to ascii IF the
839 source contains wildcards (true even if only one match) */
840 if (wcspbrk(sourcelist->name, L"*?") != NULL) {
841 anyconcats = TRUE; /* We really are concatenating to a single file */
842 if (destination->binarycopy == -1) {
843 destination->binarycopy = 0;
845 } else {
846 if (destination->binarycopy == -1) {
847 destination->binarycopy = 1;
852 /* Save away the destination name*/
853 heap_free(destination->name);
854 destination->name = heap_strdupW(destname);
855 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
856 wine_dbgstr_w(destname), appendfirstsource);
858 /* Remember if the destination is a device */
859 if (wcsncmp(destination->name, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
860 WINE_TRACE("Destination is a device\n");
861 dstisdevice = TRUE;
864 /* Now we need to walk the set of sources, and process each name we come to.
865 If anyconcats is true, we are writing to one file, otherwise we are using
866 the source name each time.
867 If destination exists, prompt for overwrite the first time (if concatenating
868 we ask each time until yes is answered)
869 The first source file we come across must exist (when wildcards expanded)
870 and if concatenating with overwrite prompts, each source file must exist
871 until a yes is answered. */
873 thiscopy = sourcelist;
874 prevcopy = NULL;
876 while (thiscopy != NULL) {
878 WCHAR srcpath[MAX_PATH];
879 const WCHAR *srcname;
880 WCHAR *filenamepart;
881 DWORD attributes;
882 BOOL srcisdevice = FALSE;
884 /* If it was not explicit, we now know whether we are concatenating or not and
885 hence whether to copy as binary or ascii */
886 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
888 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
889 to where the filename portion begins (used for wildcard expansion). */
890 GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
891 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
893 /* If parameter is a directory, ensure it ends in \* */
894 attributes = GetFileAttributesW(srcpath);
895 if (ends_with_backslash( srcpath )) {
897 /* We need to know where the filename part starts, so append * and
898 recalculate the full resulting path */
899 lstrcatW(thiscopy->name, L"*");
900 GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
901 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
903 } else if ((wcspbrk(srcpath, L"*?") == NULL) &&
904 (attributes != INVALID_FILE_ATTRIBUTES) &&
905 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
907 /* We need to know where the filename part starts, so append \* and
908 recalculate the full resulting path */
909 lstrcatW(thiscopy->name, L"\\*");
910 GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
911 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
914 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
915 wine_dbgstr_w(srcpath), anyconcats);
917 /* If the source is a device, just use it, otherwise search */
918 if (wcsncmp(srcpath, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
919 WINE_TRACE("Source is a device\n");
920 srcisdevice = TRUE;
921 srcname = &srcpath[4]; /* After the \\.\ prefix */
922 } else {
924 /* Loop through all source files */
925 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
926 hff = FindFirstFileW(srcpath, &fd);
927 if (hff != INVALID_HANDLE_VALUE) {
928 srcname = fd.cFileName;
932 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
933 do {
934 WCHAR outname[MAX_PATH];
935 BOOL overwrite;
936 BOOL appendtofirstfile = FALSE;
938 /* Skip . and .., and directories */
939 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
940 WINE_TRACE("Skipping directories\n");
941 } else {
943 /* Build final destination name */
944 lstrcpyW(outname, destination->name);
945 if (destisdirectory || appendfirstsource) lstrcatW(outname, srcname);
947 /* Build source name */
948 if (!srcisdevice) lstrcpyW(filenamepart, srcname);
950 /* Do we just overwrite (we do if we are writing to a device) */
951 overwrite = !prompt;
952 if (dstisdevice || (anyconcats && writtenoneconcat)) {
953 overwrite = TRUE;
956 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
957 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
958 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
959 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
961 if (!writtenoneconcat) {
962 appendtofirstfile = anyconcats && WCMD_IsSameFile(srcpath, outname);
965 /* Prompt before overwriting */
966 if (appendtofirstfile) {
967 overwrite = TRUE;
968 } else if (!overwrite) {
969 DWORD attributes = GetFileAttributesW(outname);
970 if (attributes != INVALID_FILE_ATTRIBUTES) {
971 WCHAR* question;
972 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
973 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
974 LocalFree(question);
976 else overwrite = TRUE;
979 /* If we needed to save away the first filename, do it */
980 if (appendfirstsource && overwrite) {
981 heap_free(destination->name);
982 destination->name = heap_strdupW(outname);
983 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
984 appendfirstsource = FALSE;
985 destisdirectory = FALSE;
988 /* Do the copy as appropriate */
989 if (overwrite) {
990 if (anyconcats && WCMD_IsSameFile(srcpath, outname)) {
991 /* Silently skip if the destination file is also a source file */
992 status = TRUE;
993 } else if (anyconcats && writtenoneconcat) {
994 if (thiscopy->binarycopy) {
995 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
996 } else {
997 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
999 } else if (!thiscopy->binarycopy) {
1000 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
1001 } else if (srcisdevice) {
1002 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
1003 } else {
1004 status = CopyFileW(srcpath, outname, FALSE);
1006 if (!status) {
1007 WCMD_print_error ();
1008 errorlevel = 1;
1009 } else {
1010 WINE_TRACE("Copied successfully\n");
1011 if (anyconcats) writtenoneconcat = TRUE;
1013 /* Append EOF if ascii destination and we are not going to add more onto the end
1014 Note: Testing shows windows has an optimization whereas if you have a binary
1015 copy of a file to a single destination (ie concatenation) then it does not add
1016 the EOF, hence the check on the source copy type below. */
1017 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
1018 if (!WCMD_AppendEOF(outname)) {
1019 WCMD_print_error ();
1020 errorlevel = 1;
1026 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1027 if (!srcisdevice) FindClose (hff);
1028 } else {
1029 /* Error if the first file was not found */
1030 if (!anyconcats || !writtenoneconcat) {
1031 WCMD_print_error ();
1032 errorlevel = 1;
1036 /* Step on to the next supplied source */
1037 thiscopy = thiscopy -> next;
1040 /* Append EOF if ascii destination and we were concatenating */
1041 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
1042 if (!WCMD_AppendEOF(destination->name)) {
1043 WCMD_print_error ();
1044 errorlevel = 1;
1048 /* Exit out of the routine, freeing any remaining allocated memory */
1049 exitreturn:
1051 thiscopy = sourcelist;
1052 while (thiscopy != NULL) {
1053 prevcopy = thiscopy;
1054 /* Free up this block*/
1055 thiscopy = thiscopy -> next;
1056 heap_free(prevcopy->name);
1057 heap_free(prevcopy);
1060 /* Free up the destination memory */
1061 if (destination) {
1062 heap_free(destination->name);
1063 heap_free(destination);
1066 return;
1069 /****************************************************************************
1070 * WCMD_create_dir
1072 * Create a directory (and, if needed, any intermediate directories).
1074 * Modifies its argument by replacing slashes temporarily with nulls.
1077 static BOOL create_full_path(WCHAR* path)
1079 WCHAR *p, *start;
1081 /* don't mess with drive letter portion of path, if any */
1082 start = path;
1083 if (path[1] == ':')
1084 start = path+2;
1086 /* Strip trailing slashes. */
1087 for (p = path + lstrlenW(path) - 1; p != start && *p == '\\'; p--)
1088 *p = 0;
1090 /* Step through path, creating intermediate directories as needed. */
1091 /* First component includes drive letter, if any. */
1092 p = start;
1093 for (;;) {
1094 DWORD rv;
1095 /* Skip to end of component */
1096 while (*p == '\\') p++;
1097 while (*p && *p != '\\') p++;
1098 if (!*p) {
1099 /* path is now the original full path */
1100 return CreateDirectoryW(path, NULL);
1102 /* Truncate path, create intermediate directory, and restore path */
1103 *p = 0;
1104 rv = CreateDirectoryW(path, NULL);
1105 *p = '\\';
1106 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1107 return FALSE;
1109 /* notreached */
1110 return FALSE;
1113 void WCMD_create_dir (WCHAR *args) {
1114 int argno = 0;
1115 WCHAR *argN = args;
1117 if (param1[0] == 0x00) {
1118 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1119 return;
1121 /* Loop through all args */
1122 while (TRUE) {
1123 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1124 if (!argN) break;
1125 if (!create_full_path(thisArg)) {
1126 WCMD_print_error ();
1127 errorlevel = 1;
1132 /* Parse the /A options given by the user on the commandline
1133 * into a bitmask of wanted attributes (*wantSet),
1134 * and a bitmask of unwanted attributes (*wantClear).
1136 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1137 WCHAR *p;
1139 /* both are strictly 'out' parameters */
1140 *wantSet=0;
1141 *wantClear=0;
1143 /* For each /A argument */
1144 for (p=wcsstr(quals, L"/A"); p != NULL; p=wcsstr(p, L"/A")) {
1145 /* Skip /A itself */
1146 p += 2;
1148 /* Skip optional : */
1149 if (*p == ':') p++;
1151 /* For each of the attribute specifier chars to this /A option */
1152 for (; *p != 0 && *p != '/'; p++) {
1153 BOOL negate = FALSE;
1154 DWORD mask = 0;
1156 if (*p == '-') {
1157 negate=TRUE;
1158 p++;
1161 /* Convert the attribute specifier to a bit in one of the masks */
1162 switch (*p) {
1163 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1164 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1165 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1166 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1167 default:
1168 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1170 if (negate)
1171 *wantClear |= mask;
1172 else
1173 *wantSet |= mask;
1178 /* If filename part of parameter is * or *.*,
1179 * and neither /Q nor /P options were given,
1180 * prompt the user whether to proceed.
1181 * Returns FALSE if user says no, TRUE otherwise.
1182 * *pPrompted is set to TRUE if the user is prompted.
1183 * (If /P supplied, del will prompt for individual files later.)
1185 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1186 if ((wcsstr(quals, L"/Q") == NULL) && (wcsstr(quals, L"/P") == NULL)) {
1187 WCHAR drive[10];
1188 WCHAR dir[MAX_PATH];
1189 WCHAR fname[MAX_PATH];
1190 WCHAR ext[MAX_PATH];
1191 WCHAR fpath[MAX_PATH];
1193 /* Convert path into actual directory spec */
1194 GetFullPathNameW(filename, ARRAY_SIZE(fpath), fpath, NULL);
1195 _wsplitpath(fpath, drive, dir, fname, ext);
1197 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1198 if ((lstrcmpW(fname, L"*") == 0) && (*ext == 0x00 || (lstrcmpW(ext, L".*") == 0))) {
1200 WCHAR question[MAXSTRING];
1202 /* Caller uses this to suppress "file not found" warning later */
1203 *pPrompted = TRUE;
1205 /* Ask for confirmation */
1206 wsprintfW(question, L"%s ", fpath);
1207 return WCMD_ask_confirm(question, TRUE, NULL);
1210 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1211 return TRUE;
1214 /* Helper function for WCMD_delete().
1215 * Deletes a single file, directory, or wildcard.
1216 * If /S was given, does it recursively.
1217 * Returns TRUE if a file was deleted.
1219 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1220 DWORD wanted_attrs;
1221 DWORD unwanted_attrs;
1222 BOOL found = FALSE;
1223 WCHAR argCopy[MAX_PATH];
1224 WIN32_FIND_DATAW fd;
1225 HANDLE hff;
1226 WCHAR fpath[MAX_PATH];
1227 WCHAR *p;
1228 BOOL handleParm = TRUE;
1230 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1232 lstrcpyW(argCopy, thisArg);
1233 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1234 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1236 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1237 /* Skip this arg if user declines to delete *.* */
1238 return FALSE;
1241 /* First, try to delete in the current directory */
1242 hff = FindFirstFileW(argCopy, &fd);
1243 if (hff == INVALID_HANDLE_VALUE) {
1244 handleParm = FALSE;
1245 } else {
1246 found = TRUE;
1249 /* Support del <dirname> by just deleting all files dirname\* */
1250 if (handleParm
1251 && (wcschr(argCopy,'*') == NULL)
1252 && (wcschr(argCopy,'?') == NULL)
1253 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1255 WCHAR modifiedParm[MAX_PATH];
1257 lstrcpyW(modifiedParm, argCopy);
1258 lstrcatW(modifiedParm, L"\\*");
1259 FindClose(hff);
1260 found = TRUE;
1261 WCMD_delete_one(modifiedParm);
1263 } else if (handleParm) {
1265 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1266 lstrcpyW (fpath, argCopy);
1267 do {
1268 p = wcsrchr (fpath, '\\');
1269 if (p != NULL) {
1270 *++p = '\0';
1271 lstrcatW (fpath, fd.cFileName);
1273 else lstrcpyW (fpath, fd.cFileName);
1274 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1275 BOOL ok;
1277 /* Handle attribute matching (/A) */
1278 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1279 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1281 /* /P means prompt for each file */
1282 if (ok && wcsstr(quals, L"/P") != NULL) {
1283 WCHAR* question;
1285 /* Ask for confirmation */
1286 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1287 ok = WCMD_ask_confirm(question, FALSE, NULL);
1288 LocalFree(question);
1291 /* Only proceed if ok to */
1292 if (ok) {
1294 /* If file is read only, and /A:r or /F supplied, delete it */
1295 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1296 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1297 wcsstr(quals, L"/F") != NULL)) {
1298 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1301 /* Now do the delete */
1302 if (!DeleteFileW(fpath)) WCMD_print_error ();
1306 } while (FindNextFileW(hff, &fd) != 0);
1307 FindClose (hff);
1310 /* Now recurse into all subdirectories handling the parameter in the same way */
1311 if (wcsstr(quals, L"/S") != NULL) {
1313 WCHAR thisDir[MAX_PATH];
1314 int cPos;
1316 WCHAR drive[10];
1317 WCHAR dir[MAX_PATH];
1318 WCHAR fname[MAX_PATH];
1319 WCHAR ext[MAX_PATH];
1321 /* Convert path into actual directory spec */
1322 GetFullPathNameW(argCopy, ARRAY_SIZE(thisDir), thisDir, NULL);
1323 _wsplitpath(thisDir, drive, dir, fname, ext);
1325 lstrcpyW(thisDir, drive);
1326 lstrcatW(thisDir, dir);
1327 cPos = lstrlenW(thisDir);
1329 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1331 /* Append '*' to the directory */
1332 thisDir[cPos] = '*';
1333 thisDir[cPos+1] = 0x00;
1335 hff = FindFirstFileW(thisDir, &fd);
1337 /* Remove residual '*' */
1338 thisDir[cPos] = 0x00;
1340 if (hff != INVALID_HANDLE_VALUE) {
1341 DIRECTORY_STACK *allDirs = NULL;
1342 DIRECTORY_STACK *lastEntry = NULL;
1344 do {
1345 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1346 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0)) {
1348 DIRECTORY_STACK *nextDir;
1349 WCHAR subParm[MAX_PATH];
1351 /* Work out search parameter in sub dir */
1352 lstrcpyW (subParm, thisDir);
1353 lstrcatW (subParm, fd.cFileName);
1354 lstrcatW (subParm, L"\\");
1355 lstrcatW (subParm, fname);
1356 lstrcatW (subParm, ext);
1357 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1359 /* Allocate memory, add to list */
1360 nextDir = heap_xalloc(sizeof(DIRECTORY_STACK));
1361 if (allDirs == NULL) allDirs = nextDir;
1362 if (lastEntry != NULL) lastEntry->next = nextDir;
1363 lastEntry = nextDir;
1364 nextDir->next = NULL;
1365 nextDir->dirName = heap_strdupW(subParm);
1367 } while (FindNextFileW(hff, &fd) != 0);
1368 FindClose (hff);
1370 /* Go through each subdir doing the delete */
1371 while (allDirs != NULL) {
1372 DIRECTORY_STACK *tempDir;
1374 tempDir = allDirs->next;
1375 found |= WCMD_delete_one (allDirs->dirName);
1377 heap_free(allDirs->dirName);
1378 heap_free(allDirs);
1379 allDirs = tempDir;
1384 return found;
1387 /****************************************************************************
1388 * WCMD_delete
1390 * Delete a file or wildcarded set.
1392 * Note on /A:
1393 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1394 * - Each set is a pattern, eg /ahr /as-r means
1395 * readonly+hidden OR nonreadonly system files
1396 * - The '-' applies to a single field, ie /a:-hr means read only
1397 * non-hidden files
1400 BOOL WCMD_delete (WCHAR *args) {
1401 int argno;
1402 WCHAR *argN;
1403 BOOL argsProcessed = FALSE;
1404 BOOL foundAny = FALSE;
1406 errorlevel = 0;
1408 for (argno=0; ; argno++) {
1409 BOOL found;
1410 WCHAR *thisArg;
1412 argN = NULL;
1413 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1414 if (!argN)
1415 break; /* no more parameters */
1416 if (argN[0] == '/')
1417 continue; /* skip options */
1419 argsProcessed = TRUE;
1420 found = WCMD_delete_one(thisArg);
1421 if (!found)
1422 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1423 foundAny |= found;
1426 /* Handle no valid args */
1427 if (!argsProcessed)
1428 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1430 return foundAny;
1434 * WCMD_strtrim
1436 * Returns a trimmed version of s with all leading and trailing whitespace removed
1437 * Pre: s non NULL
1440 static WCHAR *WCMD_strtrim(const WCHAR *s)
1442 DWORD len = lstrlenW(s);
1443 const WCHAR *start = s;
1444 WCHAR* result;
1446 result = heap_xalloc((len + 1) * sizeof(WCHAR));
1448 while (iswspace(*start)) start++;
1449 if (*start) {
1450 const WCHAR *end = s + len - 1;
1451 while (end > start && iswspace(*end)) end--;
1452 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1453 result[end - start + 1] = '\0';
1454 } else {
1455 result[0] = '\0';
1458 return result;
1461 /****************************************************************************
1462 * WCMD_echo
1464 * Echo input to the screen (or not). We don't try to emulate the bugs
1465 * in DOS (try typing "ECHO ON AGAIN" for an example).
1468 void WCMD_echo (const WCHAR *args)
1470 int count;
1471 const WCHAR *origcommand = args;
1472 WCHAR *trimmed;
1474 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1475 || args[0]==':' || args[0]==';' || args[0]=='/')
1476 args++;
1478 trimmed = WCMD_strtrim(args);
1479 if (!trimmed) return;
1481 count = lstrlenW(trimmed);
1482 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1483 && origcommand[0]!=';' && origcommand[0]!='/') {
1484 if (echo_mode) WCMD_output(WCMD_LoadMessage(WCMD_ECHOPROMPT), L"ON");
1485 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), L"OFF");
1486 heap_free(trimmed);
1487 return;
1490 if (lstrcmpiW(trimmed, L"ON") == 0)
1491 echo_mode = TRUE;
1492 else if (lstrcmpiW(trimmed, L"OFF") == 0)
1493 echo_mode = FALSE;
1494 else {
1495 WCMD_output_asis (args);
1496 WCMD_output_asis(L"\r\n");
1498 heap_free(trimmed);
1501 /*****************************************************************************
1502 * WCMD_part_execute
1504 * Execute a command, and any && or bracketed follow on to the command. The
1505 * first command to be executed may not be at the front of the
1506 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1508 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1509 BOOL isIF, BOOL executecmds)
1511 CMD_LIST *curPosition = *cmdList;
1512 int myDepth = (*cmdList)->bracketDepth;
1514 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d), isIF(%d)\n", cmdList,
1515 wine_dbgstr_w(firstcmd), executecmds, isIF);
1517 /* Skip leading whitespace between condition and the command */
1518 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1520 /* Process the first command, if there is one */
1521 if (executecmds && firstcmd && *firstcmd) {
1522 WCHAR *command = heap_strdupW(firstcmd);
1523 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1524 heap_free(command);
1528 /* If it didn't move the position, step to next command */
1529 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1531 /* Process any other parts of the command */
1532 if (*cmdList) {
1533 BOOL processThese = executecmds;
1535 while (*cmdList) {
1536 static const WCHAR ifElse[] = {'e','l','s','e'};
1538 /* execute all appropriate commands */
1539 curPosition = *cmdList;
1541 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d) processThese(%d)\n",
1542 *cmdList,
1543 (*cmdList)->prevDelim,
1544 (*cmdList)->bracketDepth,
1545 myDepth,
1546 processThese);
1548 /* Execute any statements appended to the line */
1549 /* FIXME: Only if previous call worked for && or failed for || */
1550 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1551 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1552 if (processThese && (*cmdList)->command) {
1553 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1554 cmdList, FALSE);
1556 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1558 /* Execute any appended to the statement with (...) */
1559 } else if ((*cmdList)->bracketDepth > myDepth) {
1560 if (processThese) {
1561 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1562 } else {
1563 WINE_TRACE("Skipping command %p due to stack depth\n", *cmdList);
1565 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1567 /* End of the command - does 'ELSE ' follow as the next command? */
1568 } else {
1569 if (isIF && WCMD_keyword_ws_found(ifElse, ARRAY_SIZE(ifElse), (*cmdList)->command)) {
1570 /* Swap between if and else processing */
1571 processThese = !executecmds;
1573 /* Process the ELSE part */
1574 if (processThese) {
1575 const int keyw_len = ARRAY_SIZE(ifElse) + 1;
1576 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1578 /* Skip leading whitespace between condition and the command */
1579 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1580 if (*cmd) {
1581 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1583 } else {
1584 /* Loop skipping all commands until we get back to the current
1585 depth, including skipping commands and their subsequent
1586 pipes (eg cmd | prog) */
1587 do {
1588 *cmdList = (*cmdList)->nextcommand;
1589 } while (*cmdList &&
1590 ((*cmdList)->bracketDepth > myDepth ||
1591 (*cmdList)->prevDelim));
1593 /* After the else is complete, we need to now process subsequent commands */
1594 processThese = TRUE;
1596 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1598 /* If we were in an IF statement and we didn't find an else and yet we get back to
1599 the same bracket depth as the IF, then the IF statement is over. This is required
1600 to handle nested ifs properly */
1601 } else if (isIF && (*cmdList)->bracketDepth == myDepth) {
1602 static const WCHAR doW[] = {'d','o'};
1603 if (WCMD_keyword_ws_found(doW, ARRAY_SIZE(doW), (*cmdList)->command)) {
1604 WINE_TRACE("Still inside FOR-loop, not an end of IF statement\n");
1605 *cmdList = (*cmdList)->nextcommand;
1606 } else {
1607 WINE_TRACE("Found end of this nested IF statement, ending this if\n");
1608 break;
1610 } else if (!processThese) {
1611 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1612 WINE_TRACE("Skipping this command, as in not process mode (next = %p)\n", *cmdList);
1613 } else {
1614 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1615 break;
1620 return;
1623 /*****************************************************************************
1624 * WCMD_parse_forf_options
1626 * Parses the for /f 'options', extracting the values and validating the
1627 * keywords. Note all keywords are optional.
1628 * Parameters:
1629 * options [I] The unparsed parameter string
1630 * eol [O] Set to the comment character (eol=x)
1631 * skip [O] Set to the number of lines to skip (skip=xx)
1632 * delims [O] Set to the token delimiters (delims=)
1633 * tokens [O] Set to the requested tokens, as provided (tokens=)
1634 * usebackq [O] Set to TRUE if usebackq found
1636 * Returns TRUE on success, FALSE on syntax error
1639 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1640 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1643 WCHAR *pos = options;
1644 int len = lstrlenW(pos);
1645 static const WCHAR eolW[] = {'e','o','l','='};
1646 static const WCHAR skipW[] = {'s','k','i','p','='};
1647 static const WCHAR tokensW[] = {'t','o','k','e','n','s','='};
1648 static const WCHAR delimsW[] = {'d','e','l','i','m','s','='};
1649 static const WCHAR usebackqW[] = {'u','s','e','b','a','c','k','q'};
1651 /* Initialize to defaults */
1652 lstrcpyW(delims, L" \t");
1653 lstrcpyW(tokens, L"1");
1654 *eol = 0;
1655 *skip = 0;
1656 *usebackq = FALSE;
1658 /* Strip (optional) leading and trailing quotes */
1659 if ((*pos == '"') && (pos[len-1] == '"')) {
1660 pos[len-1] = 0;
1661 pos++;
1664 /* Process each keyword */
1665 while (pos && *pos) {
1666 if (*pos == ' ' || *pos == '\t') {
1667 pos++;
1669 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1670 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1671 pos, ARRAY_SIZE(eolW), eolW, ARRAY_SIZE(eolW)) == CSTR_EQUAL) {
1672 *eol = *(pos + ARRAY_SIZE(eolW));
1673 pos = pos + ARRAY_SIZE(eolW) + 1;
1674 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1676 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1677 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1678 pos, ARRAY_SIZE(skipW), skipW, ARRAY_SIZE(skipW)) == CSTR_EQUAL) {
1679 WCHAR *nextchar = NULL;
1680 pos = pos + ARRAY_SIZE(skipW);
1681 *skip = wcstoul(pos, &nextchar, 0);
1682 WINE_TRACE("Found skip as %d lines\n", *skip);
1683 pos = nextchar;
1685 /* Save if usebackq semantics are in effect */
1686 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, pos,
1687 ARRAY_SIZE(usebackqW), usebackqW, ARRAY_SIZE(usebackqW)) == CSTR_EQUAL) {
1688 *usebackq = TRUE;
1689 pos = pos + ARRAY_SIZE(usebackqW);
1690 WINE_TRACE("Found usebackq\n");
1692 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1693 if you finish the optionsroot string with delims= otherwise the space is
1694 just a token delimiter! */
1695 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1696 pos, ARRAY_SIZE(delimsW), delimsW, ARRAY_SIZE(delimsW)) == CSTR_EQUAL) {
1697 int i=0;
1699 pos = pos + ARRAY_SIZE(delimsW);
1700 while (*pos && *pos != ' ') {
1701 delims[i++] = *pos;
1702 pos++;
1704 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1705 delims[i++] = 0; /* Null terminate the delims */
1706 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1708 /* Save the tokens being requested */
1709 } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1710 pos, ARRAY_SIZE(tokensW), tokensW, ARRAY_SIZE(tokensW)) == CSTR_EQUAL) {
1711 int i=0;
1713 pos = pos + ARRAY_SIZE(tokensW);
1714 while (*pos && *pos != ' ') {
1715 tokens[i++] = *pos;
1716 pos++;
1718 tokens[i++] = 0; /* Null terminate the tokens */
1719 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1721 } else {
1722 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1723 return FALSE;
1726 return TRUE;
1729 /*****************************************************************************
1730 * WCMD_add_dirstowalk
1732 * When recursing through directories (for /r), we need to add to the list of
1733 * directories still to walk, any subdirectories of the one we are processing.
1735 * Parameters
1736 * options [I] The remaining list of directories still to process
1738 * Note this routine inserts the subdirectories found between the entry being
1739 * processed, and any other directory still to be processed, mimicking what
1740 * Windows does
1742 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1743 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1744 WCHAR fullitem[MAX_PATH];
1745 WIN32_FIND_DATAW fd;
1746 HANDLE hff;
1748 /* Build a generic search and add all directories on the list of directories
1749 still to walk */
1750 lstrcpyW(fullitem, dirsToWalk->dirName);
1751 lstrcatW(fullitem, L"\\*");
1752 hff = FindFirstFileW(fullitem, &fd);
1753 if (hff != INVALID_HANDLE_VALUE) {
1754 do {
1755 WINE_TRACE("Looking for subdirectories\n");
1756 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1757 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
1759 /* Allocate memory, add to list */
1760 DIRECTORY_STACK *toWalk = heap_xalloc(sizeof(DIRECTORY_STACK));
1761 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1762 toWalk->next = remainingDirs->next;
1763 remainingDirs->next = toWalk;
1764 remainingDirs = toWalk;
1765 toWalk->dirName = heap_xalloc(sizeof(WCHAR) * (lstrlenW(dirsToWalk->dirName) + 2 + lstrlenW(fd.cFileName)));
1766 lstrcpyW(toWalk->dirName, dirsToWalk->dirName);
1767 lstrcatW(toWalk->dirName, L"\\");
1768 lstrcatW(toWalk->dirName, fd.cFileName);
1769 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1770 toWalk, toWalk->next);
1772 } while (FindNextFileW(hff, &fd) != 0);
1773 WINE_TRACE("Finished adding all subdirectories\n");
1774 FindClose (hff);
1778 /**************************************************************************
1779 * WCMD_for_nexttoken
1781 * Parse the token= line, identifying the next highest number not processed
1782 * so far. Count how many tokens are referred (including duplicates) and
1783 * optionally return that, plus optionally indicate if the tokens= line
1784 * ends in a star.
1786 * Parameters:
1787 * lasttoken [I] - Identifies the token index of the last one
1788 * returned so far (-1 used for first loop)
1789 * tokenstr [I] - The specified tokens= line
1790 * firstCmd [O] - Optionally indicate how many tokens are listed
1791 * doAll [O] - Optionally indicate if line ends with *
1792 * duplicates [O] - Optionally indicate if there is any evidence of
1793 * overlaying tokens in the string
1794 * Note the caller should keep a running track of duplicates as the tokens
1795 * are recursively passed. If any have duplicates, then the * token should
1796 * not be honoured.
1798 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1799 int *totalfound, BOOL *doall,
1800 BOOL *duplicates)
1802 WCHAR *pos = tokenstr;
1803 int nexttoken = -1;
1805 if (totalfound) *totalfound = 0;
1806 if (doall) *doall = FALSE;
1807 if (duplicates) *duplicates = FALSE;
1809 WINE_TRACE("Find next token after %d in %s\n", lasttoken,
1810 wine_dbgstr_w(tokenstr));
1812 /* Loop through the token string, parsing it. Valid syntax is:
1813 token=m or x-y with comma delimiter and optionally * to finish*/
1814 while (*pos) {
1815 int nextnumber1, nextnumber2 = -1;
1816 WCHAR *nextchar;
1818 /* Remember if the next character is a star, it indicates a need to
1819 show all remaining tokens and should be the last character */
1820 if (*pos == '*') {
1821 if (doall) *doall = TRUE;
1822 if (totalfound) (*totalfound)++;
1823 /* If we have not found a next token to return, then indicate
1824 time to process the star */
1825 if (nexttoken == -1) {
1826 if (lasttoken == -1) {
1827 /* Special case the syntax of tokens=* which just means get whole line */
1828 nexttoken = 0;
1829 } else {
1830 nexttoken = lasttoken;
1833 break;
1836 /* Get the next number */
1837 nextnumber1 = wcstoul(pos, &nextchar, 10);
1839 /* If it is followed by a minus, it's a range, so get the next one as well */
1840 if (*nextchar == '-') {
1841 nextnumber2 = wcstoul(nextchar+1, &nextchar, 10);
1843 /* We want to return the lowest number that is higher than lasttoken
1844 but only if range is positive */
1845 if (nextnumber2 >= nextnumber1 &&
1846 lasttoken < nextnumber2) {
1848 int nextvalue;
1849 if (nexttoken == -1) {
1850 nextvalue = max(nextnumber1, (lasttoken+1));
1851 } else {
1852 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1855 /* Flag if duplicates identified */
1856 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1858 nexttoken = nextvalue;
1861 /* Update the running total for the whole range */
1862 if (nextnumber2 >= nextnumber1 && totalfound) {
1863 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1865 pos = nextchar;
1867 } else if (pos != nextchar) {
1868 if (totalfound) (*totalfound)++;
1870 /* See if the number found is one we have already seen */
1871 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1873 /* We want to return the lowest number that is higher than lasttoken */
1874 if (lasttoken < nextnumber1 &&
1875 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1876 nexttoken = nextnumber1;
1878 pos = nextchar;
1880 } else {
1881 /* Step on to the next character, usually over comma */
1882 if (*pos) pos++;
1887 /* Return result */
1888 if (nexttoken == -1) {
1889 WINE_TRACE("No next token found, previous was %d\n", lasttoken);
1890 nexttoken = lasttoken;
1891 } else if (nexttoken==lasttoken && doall && *doall) {
1892 WINE_TRACE("Request for all remaining tokens now\n");
1893 } else {
1894 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1896 if (totalfound) WINE_TRACE("Found total tokens to be %d\n", *totalfound);
1897 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1898 return nexttoken;
1901 /**************************************************************************
1902 * WCMD_parse_line
1904 * When parsing file or string contents (for /f), once the string to parse
1905 * has been identified, handle the various options and call the do part
1906 * if appropriate.
1908 * Parameters:
1909 * cmdStart [I] - Identifies the list of commands making up the
1910 * for loop body (especially if brackets in use)
1911 * firstCmd [I] - The textual start of the command after the DO
1912 * which is within the first item of cmdStart
1913 * cmdEnd [O] - Identifies where to continue after the DO
1914 * variable [I] - The variable identified on the for line
1915 * buffer [I] - The string to parse
1916 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1917 * forf_skip [I/O] - How many lines to skip first
1918 * forf_eol [I] - The 'end of line' (comment) character
1919 * forf_delims [I] - The delimiters to use when breaking the string apart
1920 * forf_tokens [I] - The tokens to use when breaking the string apart
1922 static void WCMD_parse_line(CMD_LIST *cmdStart,
1923 const WCHAR *firstCmd,
1924 CMD_LIST **cmdEnd,
1925 const WCHAR variable,
1926 WCHAR *buffer,
1927 BOOL *doExecuted,
1928 int *forf_skip,
1929 WCHAR forf_eol,
1930 WCHAR *forf_delims,
1931 WCHAR *forf_tokens) {
1933 WCHAR *parm;
1934 FOR_CONTEXT oldcontext;
1935 int varidx, varoffset;
1936 int nexttoken, lasttoken = -1;
1937 BOOL starfound = FALSE;
1938 BOOL thisduplicate = FALSE;
1939 BOOL anyduplicates = FALSE;
1940 int totalfound;
1941 static WCHAR emptyW[] = L"";
1943 /* Skip lines if requested */
1944 if (*forf_skip) {
1945 (*forf_skip)--;
1946 return;
1949 /* Save away any existing for variable context (e.g. nested for loops) */
1950 oldcontext = forloopcontext;
1952 /* Extract the parameters based on the tokens= value (There will always
1953 be some value, as if it is not supplied, it defaults to tokens=1).
1954 Rough logic:
1955 Count how many tokens are named in the line, identify the lowest
1956 Empty (set to null terminated string) that number of named variables
1957 While lasttoken != nextlowest
1958 %letter = parameter number 'nextlowest'
1959 letter++ (if >26 or >52 abort)
1960 Go through token= string finding next lowest number
1961 If token ends in * set %letter = raw position of token(nextnumber+1)
1963 lasttoken = -1;
1964 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1965 &starfound, &thisduplicate);
1966 varidx = FOR_VAR_IDX(variable);
1968 /* Empty out variables */
1969 for (varoffset=0;
1970 varidx >= 0 && varoffset<totalfound && (((varidx%26) + varoffset) < 26);
1971 varoffset++) {
1972 forloopcontext.variable[varidx + varoffset] = emptyW;
1975 /* Loop extracting the tokens
1976 Note: nexttoken of 0 means there were no tokens requested, to handle
1977 the special case of tokens=* */
1978 varoffset = 0;
1979 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1980 while (varidx >= 0 && (nexttoken > 0 && (nexttoken > lasttoken))) {
1981 anyduplicates |= thisduplicate;
1983 /* Extract the token number requested and set into the next variable context */
1984 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims);
1985 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
1986 varidx + varoffset, wine_dbgstr_w(parm));
1987 if (varidx >=0) {
1988 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
1989 varoffset++;
1990 if (((varidx%26)+varoffset) >= 26) break;
1993 /* Find the next token */
1994 lasttoken = nexttoken;
1995 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
1996 &starfound, &thisduplicate);
1999 /* If all the rest of the tokens were requested, and there is still space in
2000 the variable range, write them now */
2001 if (!anyduplicates && starfound && varidx >= 0 && (((varidx%26) + varoffset) < 26)) {
2002 nexttoken++;
2003 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
2004 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2005 varidx + varoffset, wine_dbgstr_w(parm));
2006 if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
2009 /* Execute the body of the foor loop with these values */
2010 if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
2011 CMD_LIST *thisCmdStart = cmdStart;
2012 *doExecuted = TRUE;
2013 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
2014 *cmdEnd = thisCmdStart;
2017 /* Free the duplicated strings, and restore the context */
2018 if (varidx >=0) {
2019 int i;
2020 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
2021 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
2022 (forloopcontext.variable[i] != emptyW)) {
2023 heap_free(forloopcontext.variable[i]);
2028 /* Restore the original for variable contextx */
2029 forloopcontext = oldcontext;
2032 /**************************************************************************
2033 * WCMD_forf_getinputhandle
2035 * Return a file handle which can be used for reading the input lines,
2036 * either to a specific file (which may be quote delimited as we have to
2037 * read the parameters in raw mode) or to a command which we need to
2038 * execute. The command being executed runs in its own shell and stores
2039 * its data in a temporary file.
2041 * Parameters:
2042 * usebackq [I] - Indicates whether usebackq is in effect or not
2043 * itemStr [I] - The item to be handled, either a filename or
2044 * whole command string to execute
2045 * iscmd [I] - Identifies whether this is a command or not
2047 * Returns a file handle which can be used to read the input lines from.
2049 static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2050 WCHAR temp_str[MAX_PATH];
2051 WCHAR temp_file[MAX_PATH];
2052 WCHAR temp_cmd[MAXSTRING];
2053 WCHAR *trimmed = NULL;
2054 HANDLE hinput = INVALID_HANDLE_VALUE;
2056 /* Remove leading and trailing character (but there may be trailing whitespace too) */
2057 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2058 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2059 (!iscmd && (itemstr[0] == '"' && usebackq)))
2061 trimmed = WCMD_strtrim(itemstr);
2062 if (trimmed) {
2063 itemstr = trimmed;
2065 itemstr[lstrlenW(itemstr)-1] = 0x00;
2066 itemstr++;
2069 if (iscmd) {
2070 /* Get temp filename */
2071 GetTempPathW(ARRAY_SIZE(temp_str), temp_str);
2072 GetTempFileNameW(temp_str, L"CMD", 0, temp_file);
2074 /* Redirect output to the temporary file */
2075 wsprintfW(temp_str, L">%s", temp_file);
2076 wsprintfW(temp_cmd, L"CMD.EXE /C %s", itemstr);
2077 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2078 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2079 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2081 /* Open the file, read line by line and process */
2082 hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
2083 NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
2085 } else {
2086 /* Open the file, read line by line and process */
2087 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2088 hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
2089 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2091 heap_free(trimmed);
2092 return hinput;
2095 /**************************************************************************
2096 * WCMD_for
2098 * Batch file loop processing.
2100 * On entry: cmdList contains the syntax up to the set
2101 * next cmdList and all in that bracket contain the set data
2102 * next cmdlist contains the DO cmd
2103 * following that is either brackets or && entries (as per if)
2107 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2109 WIN32_FIND_DATAW fd;
2110 HANDLE hff;
2111 int i;
2112 static const WCHAR inW[] = {'i','n'};
2113 static const WCHAR doW[] = {'d','o'};
2114 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2115 WCHAR variable[4];
2116 int varidx = -1;
2117 WCHAR *oldvariablevalue;
2118 WCHAR *firstCmd;
2119 int thisDepth;
2120 WCHAR optionsRoot[MAX_PATH];
2121 DIRECTORY_STACK *dirsToWalk = NULL;
2122 BOOL expandDirs = FALSE;
2123 BOOL useNumbers = FALSE;
2124 BOOL doFileset = FALSE;
2125 BOOL doRecurse = FALSE;
2126 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2127 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2128 int itemNum;
2129 CMD_LIST *thisCmdStart;
2130 int parameterNo = 0;
2131 WCHAR forf_eol = 0;
2132 int forf_skip = 0;
2133 WCHAR forf_delims[256];
2134 WCHAR forf_tokens[MAXSTRING];
2135 BOOL forf_usebackq = FALSE;
2137 /* Handle optional qualifiers (multiple are allowed) */
2138 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2140 optionsRoot[0] = 0;
2141 while (thisArg && *thisArg == '/') {
2142 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2143 thisArg++;
2144 switch (towupper(*thisArg)) {
2145 case 'D': expandDirs = TRUE; break;
2146 case 'L': useNumbers = TRUE; break;
2148 /* Recursive is special case - /R can have an optional path following it */
2149 /* filenamesets are another special case - /F can have an optional options following it */
2150 case 'R':
2151 case 'F':
2153 /* When recursing directories, use current directory as the starting point unless
2154 subsequently overridden */
2155 doRecurse = (towupper(*thisArg) == 'R');
2156 if (doRecurse) GetCurrentDirectoryW(ARRAY_SIZE(optionsRoot), optionsRoot);
2158 doFileset = (towupper(*thisArg) == 'F');
2160 /* Retrieve next parameter to see if is root/options (raw form required
2161 with for /f, or unquoted in for /r) */
2162 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2164 /* Next parm is either qualifier, path/options or variable -
2165 only care about it if it is the path/options */
2166 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2167 parameterNo++;
2168 lstrcpyW(optionsRoot, thisArg);
2170 break;
2172 default:
2173 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2176 /* Step to next token */
2177 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2180 /* Ensure line continues with variable */
2181 if (*thisArg != '%') {
2182 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2183 return;
2186 /* With for /f parse the options if provided */
2187 if (doFileset) {
2188 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2189 forf_delims, forf_tokens, &forf_usebackq))
2191 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2192 return;
2195 /* Set up the list of directories to recurse if we are going to */
2196 } else if (doRecurse) {
2197 /* Allocate memory, add to list */
2198 dirsToWalk = heap_xalloc(sizeof(DIRECTORY_STACK));
2199 dirsToWalk->next = NULL;
2200 dirsToWalk->dirName = heap_strdupW(optionsRoot);
2201 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2204 /* Variable should follow */
2205 lstrcpyW(variable, thisArg);
2206 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2207 varidx = FOR_VAR_IDX(variable[1]);
2209 /* Ensure line continues with IN */
2210 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2211 if (!thisArg
2212 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2213 thisArg, ARRAY_SIZE(inW), inW, ARRAY_SIZE(inW)) == CSTR_EQUAL)) {
2214 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2215 return;
2218 /* Save away where the set of data starts and the variable */
2219 thisDepth = (*cmdList)->bracketDepth;
2220 *cmdList = (*cmdList)->nextcommand;
2221 setStart = (*cmdList);
2223 /* Skip until the close bracket */
2224 WINE_TRACE("Searching %p as the set\n", *cmdList);
2225 while (*cmdList &&
2226 (*cmdList)->command != NULL &&
2227 (*cmdList)->bracketDepth > thisDepth) {
2228 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2229 *cmdList = (*cmdList)->nextcommand;
2232 /* Skip the close bracket, if there is one */
2233 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2235 /* Syntax error if missing close bracket, or nothing following it
2236 and once we have the complete set, we expect a DO */
2237 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2238 if ((*cmdList == NULL) || !WCMD_keyword_ws_found(doW, ARRAY_SIZE(doW), (*cmdList)->command)) {
2239 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2240 return;
2243 cmdEnd = *cmdList;
2245 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2246 mode, or once for the rest of the time. */
2247 do {
2249 /* Save away the starting position for the commands (and offset for the
2250 first one) */
2251 cmdStart = *cmdList;
2252 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2253 itemNum = 0;
2255 /* If we are recursing directories (ie /R), add all sub directories now, then
2256 prefix the root when searching for the item */
2257 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2259 thisSet = setStart;
2260 /* Loop through all set entries */
2261 while (thisSet &&
2262 thisSet->command != NULL &&
2263 thisSet->bracketDepth >= thisDepth) {
2265 /* Loop through all entries on the same line */
2266 WCHAR *staticitem;
2267 WCHAR *itemStart;
2268 WCHAR buffer[MAXSTRING];
2270 WINE_TRACE("Processing for set %p\n", thisSet);
2271 i = 0;
2272 while (*(staticitem = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2275 * If the parameter within the set has a wildcard then search for matching files
2276 * otherwise do a literal substitution.
2279 /* Take a copy of the item returned from WCMD_parameter as it is held in a
2280 static buffer which can be overwritten during parsing of the for body */
2281 WCHAR item[MAXSTRING];
2282 lstrcpyW(item, staticitem);
2284 thisCmdStart = cmdStart;
2286 itemNum++;
2287 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2289 if (!useNumbers && !doFileset) {
2290 WCHAR fullitem[MAX_PATH];
2291 int prefixlen = 0;
2293 /* Now build the item to use / search for in the specified directory,
2294 as it is fully qualified in the /R case */
2295 if (dirsToWalk) {
2296 lstrcpyW(fullitem, dirsToWalk->dirName);
2297 lstrcatW(fullitem, L"\\");
2298 lstrcatW(fullitem, item);
2299 } else {
2300 WCHAR *prefix = wcsrchr(item, '\\');
2301 if (prefix) prefixlen = (prefix - item) + 1;
2302 lstrcpyW(fullitem, item);
2305 if (wcspbrk(fullitem, L"*?")) {
2306 hff = FindFirstFileW(fullitem, &fd);
2307 if (hff != INVALID_HANDLE_VALUE) {
2308 do {
2309 BOOL isDirectory = FALSE;
2311 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2313 /* Handle as files or dirs appropriately, but ignore . and .. */
2314 if (isDirectory == expandDirs &&
2315 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
2317 thisCmdStart = cmdStart;
2318 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2320 if (doRecurse) {
2321 lstrcpyW(fullitem, dirsToWalk->dirName);
2322 lstrcatW(fullitem, L"\\");
2323 lstrcatW(fullitem, fd.cFileName);
2324 } else {
2325 if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
2326 fullitem[prefixlen] = 0x00;
2327 lstrcatW(fullitem, fd.cFileName);
2329 doExecuted = TRUE;
2331 /* Save away any existing for variable context (e.g. nested for loops)
2332 and restore it after executing the body of this for loop */
2333 if (varidx >= 0) {
2334 oldvariablevalue = forloopcontext.variable[varidx];
2335 forloopcontext.variable[varidx] = fullitem;
2337 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2338 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2340 cmdEnd = thisCmdStart;
2342 } while (FindNextFileW(hff, &fd) != 0);
2343 FindClose (hff);
2345 } else {
2346 doExecuted = TRUE;
2348 /* Save away any existing for variable context (e.g. nested for loops)
2349 and restore it after executing the body of this for loop */
2350 if (varidx >= 0) {
2351 oldvariablevalue = forloopcontext.variable[varidx];
2352 forloopcontext.variable[varidx] = fullitem;
2354 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2355 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2357 cmdEnd = thisCmdStart;
2360 } else if (useNumbers) {
2361 /* Convert the first 3 numbers to signed longs and save */
2362 if (itemNum <=3) numbers[itemNum-1] = wcstol(item, NULL, 10);
2363 /* else ignore them! */
2365 /* Filesets - either a list of files, or a command to run and parse the output */
2366 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2367 (forf_usebackq && *itemStart != '\''))) {
2369 HANDLE input;
2370 WCHAR *itemparm;
2372 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2373 wine_dbgstr_w(item));
2375 /* If backquote or single quote, we need to launch that command
2376 and parse the results - use a temporary file */
2377 if ((forf_usebackq && *itemStart == '`') ||
2378 (!forf_usebackq && *itemStart == '\'')) {
2380 /* Use itemstart because the command is the whole set, not just the first token */
2381 itemparm = itemStart;
2382 } else {
2384 /* Use item because the file to process is just the first item in the set */
2385 itemparm = item;
2387 input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
2389 /* Process the input file */
2390 if (input == INVALID_HANDLE_VALUE) {
2391 WCMD_print_error ();
2392 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2393 errorlevel = 1;
2394 return; /* FOR loop aborts at first failure here */
2396 } else {
2398 /* Read line by line until end of file */
2399 while (WCMD_fgets(buffer, ARRAY_SIZE(buffer), input)) {
2400 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2401 &forf_skip, forf_eol, forf_delims, forf_tokens);
2402 buffer[0] = 0;
2404 CloseHandle (input);
2407 /* When we have processed the item as a whole command, abort future set processing */
2408 if (itemparm==itemStart) {
2409 thisSet = NULL;
2410 break;
2413 /* Filesets - A string literal */
2414 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2415 (forf_usebackq && *itemStart == '\''))) {
2417 /* Remove leading and trailing character, ready to parse with delims= delimiters
2418 Note that the last quote is removed from the set and the string terminates
2419 there to mimic windows */
2420 WCHAR *strend = wcsrchr(itemStart, forf_usebackq?'\'':'"');
2421 if (strend) {
2422 *strend = 0x00;
2423 itemStart++;
2426 /* Copy the item away from the global buffer used by WCMD_parameter */
2427 lstrcpyW(buffer, itemStart);
2428 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2429 &forf_skip, forf_eol, forf_delims, forf_tokens);
2431 /* Only one string can be supplied in the whole set, abort future set processing */
2432 thisSet = NULL;
2433 break;
2436 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2437 i++;
2440 /* Move onto the next set line */
2441 if (thisSet) thisSet = thisSet->nextcommand;
2444 /* If /L is provided, now run the for loop */
2445 if (useNumbers) {
2446 WCHAR thisNum[20];
2448 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
2449 numbers[0], numbers[2], numbers[1]);
2450 for (i=numbers[0];
2451 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2452 i=i + numbers[1]) {
2454 swprintf(thisNum, ARRAY_SIZE(thisNum), L"%d", i);
2455 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2457 thisCmdStart = cmdStart;
2458 doExecuted = TRUE;
2460 /* Save away any existing for variable context (e.g. nested for loops)
2461 and restore it after executing the body of this for loop */
2462 if (varidx >= 0) {
2463 oldvariablevalue = forloopcontext.variable[varidx];
2464 forloopcontext.variable[varidx] = thisNum;
2466 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2467 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2469 cmdEnd = thisCmdStart;
2472 /* If we are walking directories, move on to any which remain */
2473 if (dirsToWalk != NULL) {
2474 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2475 heap_free(dirsToWalk->dirName);
2476 heap_free(dirsToWalk);
2477 dirsToWalk = nextDir;
2478 if (dirsToWalk) WINE_TRACE("Moving to next directory to iterate: %s\n",
2479 wine_dbgstr_w(dirsToWalk->dirName));
2480 else WINE_TRACE("Finished all directories.\n");
2483 } while (dirsToWalk != NULL);
2485 /* Now skip over the do part if we did not perform the for loop so far.
2486 We store in cmdEnd the next command after the do block, but we only
2487 know this if something was run. If it has not been, we need to calculate
2488 it. */
2489 if (!doExecuted) {
2490 thisCmdStart = cmdStart;
2491 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2492 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2493 cmdEnd = thisCmdStart;
2496 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2497 all processing, OR it should be pointing to the end of && processing OR
2498 it should be pointing at the NULL end of bracket for the DO. The return
2499 value needs to be the NEXT command to execute, which it either is, or
2500 we need to step over the closing bracket */
2501 *cmdList = cmdEnd;
2502 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2505 /**************************************************************************
2506 * WCMD_give_help
2508 * Simple on-line help. Help text is stored in the resource file.
2511 void WCMD_give_help (const WCHAR *args)
2513 size_t i;
2515 args = WCMD_skip_leading_spaces((WCHAR*) args);
2516 if (!*args) {
2517 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2519 else {
2520 /* Display help message for builtin commands */
2521 for (i=0; i<=WCMD_EXIT; i++) {
2522 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2523 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2524 WCMD_output_asis (WCMD_LoadMessage(i));
2525 return;
2528 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2529 for (i = 0; i <= ARRAY_SIZE(externals); i++) {
2530 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2531 args, -1, externals[i], -1) == CSTR_EQUAL) {
2532 WCHAR cmd[128];
2533 lstrcpyW(cmd, args);
2534 lstrcatW(cmd, L" /?");
2535 WCMD_run_program(cmd, FALSE);
2536 return;
2539 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2541 return;
2544 /****************************************************************************
2545 * WCMD_go_to
2547 * Batch file jump instruction. Not the most efficient algorithm ;-)
2548 * Prints error message if the specified label cannot be found - the file pointer is
2549 * then at EOF, effectively stopping the batch file.
2550 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2553 void WCMD_goto (CMD_LIST **cmdList) {
2555 WCHAR string[MAX_PATH];
2556 WCHAR *labelend = NULL;
2557 const WCHAR labelEndsW[] = L"><|& :\t";
2559 /* Do not process any more parts of a processed multipart or multilines command */
2560 if (cmdList) *cmdList = NULL;
2562 if (context != NULL) {
2563 WCHAR *paramStart = param1, *str;
2565 if (param1[0] == 0x00) {
2566 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2567 return;
2570 /* Handle special :EOF label */
2571 if (lstrcmpiW(L":eof", param1) == 0) {
2572 context -> skip_rest = TRUE;
2573 return;
2576 /* Support goto :label as well as goto label plus remove trailing chars */
2577 if (*paramStart == ':') paramStart++;
2578 labelend = wcspbrk(paramStart, labelEndsW);
2579 if (labelend) *labelend = 0x00;
2580 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
2582 /* Loop through potentially twice - once from current file position
2583 through to the end, and second time from start to current file
2584 position */
2585 if (*paramStart) {
2586 int loop;
2587 LARGE_INTEGER startli;
2588 for (loop=0; loop<2; loop++) {
2589 if (loop==0) {
2590 /* On first loop, save the file size */
2591 startli.QuadPart = 0;
2592 startli.u.LowPart = SetFilePointer(context -> h, startli.u.LowPart,
2593 &startli.u.HighPart, FILE_CURRENT);
2594 } else {
2595 /* On second loop, start at the beginning of the file */
2596 WINE_TRACE("Label not found, trying from beginning of file\n");
2597 if (loop==1) SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2600 while (WCMD_fgets (string, ARRAY_SIZE(string), context -> h)) {
2601 str = string;
2603 /* Ignore leading whitespace or no-echo character */
2604 while (*str=='@' || iswspace (*str)) str++;
2606 /* If the first real character is a : then this is a label */
2607 if (*str == ':') {
2608 str++;
2610 /* Skip spaces between : and label */
2611 while (iswspace (*str)) str++;
2612 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
2614 /* Label ends at whitespace or redirection characters */
2615 labelend = wcspbrk(str, labelEndsW);
2616 if (labelend) *labelend = 0x00;
2617 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
2619 if (lstrcmpiW (str, paramStart) == 0) return;
2622 /* See if we have gone beyond the end point if second time through */
2623 if (loop==1) {
2624 LARGE_INTEGER curli;
2625 curli.QuadPart = 0;
2626 curli.u.LowPart = SetFilePointer(context -> h, curli.u.LowPart,
2627 &curli.u.HighPart, FILE_CURRENT);
2628 if (curli.QuadPart > startli.QuadPart) {
2629 WINE_TRACE("Reached wrap point, label not found\n");
2630 break;
2637 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2638 context -> skip_rest = TRUE;
2640 return;
2643 /*****************************************************************************
2644 * WCMD_pushd
2646 * Push a directory onto the stack
2649 void WCMD_pushd (const WCHAR *args)
2651 struct env_stack *curdir;
2652 WCHAR *thisdir;
2654 if (wcschr(args, '/') != NULL) {
2655 SetLastError(ERROR_INVALID_PARAMETER);
2656 WCMD_print_error();
2657 return;
2660 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2661 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2662 if( !curdir || !thisdir ) {
2663 LocalFree(curdir);
2664 LocalFree(thisdir);
2665 WINE_ERR ("out of memory\n");
2666 return;
2669 /* Change directory using CD code with /D parameter */
2670 lstrcpyW(quals, L"/D");
2671 GetCurrentDirectoryW (1024, thisdir);
2672 errorlevel = 0;
2673 WCMD_setshow_default(args);
2674 if (errorlevel) {
2675 LocalFree(curdir);
2676 LocalFree(thisdir);
2677 return;
2678 } else {
2679 curdir -> next = pushd_directories;
2680 curdir -> strings = thisdir;
2681 if (pushd_directories == NULL) {
2682 curdir -> u.stackdepth = 1;
2683 } else {
2684 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2686 pushd_directories = curdir;
2691 /*****************************************************************************
2692 * WCMD_popd
2694 * Pop a directory from the stack
2697 void WCMD_popd (void) {
2698 struct env_stack *temp = pushd_directories;
2700 if (!pushd_directories)
2701 return;
2703 /* pop the old environment from the stack, and make it the current dir */
2704 pushd_directories = temp->next;
2705 SetCurrentDirectoryW(temp->strings);
2706 LocalFree (temp->strings);
2707 LocalFree (temp);
2710 /*******************************************************************
2711 * evaluate_if_comparison
2713 * Evaluates an "if" comparison operation
2715 * PARAMS
2716 * leftOperand [I] left operand, non NULL
2717 * operator [I] "if" binary comparison operator, non NULL
2718 * rightOperand [I] right operand, non NULL
2719 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2721 * RETURNS
2722 * Success: 1 if operator applied to the operands evaluates to TRUE
2723 * 0 if operator applied to the operands evaluates to FALSE
2724 * Failure: -1 if operator is not recognized
2726 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2727 const WCHAR *rightOperand, int caseInsensitive)
2729 WCHAR *endptr_leftOp, *endptr_rightOp;
2730 long int leftOperand_int, rightOperand_int;
2731 BOOL int_operands;
2733 /* == is a special case, as it always compares strings */
2734 if (!lstrcmpiW(operator, L"=="))
2735 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2736 : lstrcmpW (leftOperand, rightOperand) == 0;
2738 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2739 leftOperand_int = wcstol(leftOperand, &endptr_leftOp, 0);
2740 rightOperand_int = wcstol(rightOperand, &endptr_rightOp, 0);
2741 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2743 /* Perform actual (integer or string) comparison */
2744 if (!lstrcmpiW(operator, L"lss")) {
2745 if (int_operands)
2746 return leftOperand_int < rightOperand_int;
2747 else
2748 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2749 : lstrcmpW (leftOperand, rightOperand) < 0;
2752 if (!lstrcmpiW(operator, L"leq")) {
2753 if (int_operands)
2754 return leftOperand_int <= rightOperand_int;
2755 else
2756 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2757 : lstrcmpW (leftOperand, rightOperand) <= 0;
2760 if (!lstrcmpiW(operator, L"equ")) {
2761 if (int_operands)
2762 return leftOperand_int == rightOperand_int;
2763 else
2764 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2765 : lstrcmpW (leftOperand, rightOperand) == 0;
2768 if (!lstrcmpiW(operator, L"neq")) {
2769 if (int_operands)
2770 return leftOperand_int != rightOperand_int;
2771 else
2772 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2773 : lstrcmpW (leftOperand, rightOperand) != 0;
2776 if (!lstrcmpiW(operator, L"geq")) {
2777 if (int_operands)
2778 return leftOperand_int >= rightOperand_int;
2779 else
2780 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2781 : lstrcmpW (leftOperand, rightOperand) >= 0;
2784 if (!lstrcmpiW(operator, L"gtr")) {
2785 if (int_operands)
2786 return leftOperand_int > rightOperand_int;
2787 else
2788 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2789 : lstrcmpW (leftOperand, rightOperand) > 0;
2792 return -1;
2795 int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate)
2797 WCHAR condition[MAX_PATH];
2798 int caseInsensitive = (wcsstr(quals, L"/I") != NULL);
2800 *negate = !lstrcmpiW(param1,L"not");
2801 lstrcpyW(condition, (*negate ? param2 : param1));
2802 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2804 if (!lstrcmpiW(condition, L"errorlevel")) {
2805 WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
2806 WCHAR *endptr;
2807 long int param_int = wcstol(param, &endptr, 10);
2808 if (*endptr) goto syntax_err;
2809 *test = ((long int)errorlevel >= param_int);
2810 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2812 else if (!lstrcmpiW(condition, L"exist")) {
2813 WIN32_FIND_DATAW fd;
2814 HANDLE hff;
2815 WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
2816 int len = lstrlenW(param);
2818 /* FindFirstFile does not like a directory path ending in '\', append a '.' */
2819 if (len && param[len-1] == '\\') lstrcatW(param, L".");
2821 hff = FindFirstFileW(param, &fd);
2822 *test = (hff != INVALID_HANDLE_VALUE );
2823 if (*test) FindClose(hff);
2825 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2827 else if (!lstrcmpiW(condition, L"defined")) {
2828 *test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE),
2829 NULL, 0) > 0);
2830 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2832 else { /* comparison operation */
2833 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2834 WCHAR *paramStart;
2836 lstrcpyW(leftOperand, WCMD_parameter(p, (*negate)+caseInsensitive, &paramStart, TRUE, FALSE));
2837 if (!*leftOperand)
2838 goto syntax_err;
2840 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2841 p = paramStart + lstrlenW(leftOperand);
2842 while (*p == ' ' || *p == '\t')
2843 p++;
2845 if (!wcsncmp(p, L"==", lstrlenW(L"==")))
2846 lstrcpyW(operator, L"==");
2847 else {
2848 lstrcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2849 if (!*operator) goto syntax_err;
2851 p += lstrlenW(operator);
2853 lstrcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2854 if (!*rightOperand)
2855 goto syntax_err;
2857 *test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2858 if (*test == -1)
2859 goto syntax_err;
2861 p = paramStart + lstrlenW(rightOperand);
2862 WCMD_parameter(p, 0, command, FALSE, FALSE);
2865 return 1;
2867 syntax_err:
2868 return -1;
2871 /****************************************************************************
2872 * WCMD_if
2874 * Batch file conditional.
2876 * On entry, cmdlist will point to command containing the IF, and optionally
2877 * the first command to execute (if brackets not found)
2878 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2879 * If ('s were found, execute all within that bracket
2880 * Command may optionally be followed by an ELSE - need to skip instructions
2881 * in the else using the same logic
2883 * FIXME: Much more syntax checking needed!
2885 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2887 int negate; /* Negate condition */
2888 int test; /* Condition evaluation result */
2889 WCHAR *command;
2891 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
2892 set in a call to WCMD_parse before */
2893 if (evaluate_if_condition(p, &command, &test, &negate) == -1)
2894 goto syntax_err;
2896 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n",
2897 wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
2898 wine_dbgstr_w(param2), wine_dbgstr_w(command));
2900 /* Process rest of IF statement which is on the same line
2901 Note: This may process all or some of the cmdList (eg a GOTO) */
2902 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2903 return;
2905 syntax_err:
2906 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2909 /****************************************************************************
2910 * WCMD_move
2912 * Move a file, directory tree or wildcarded set of files.
2915 void WCMD_move (void)
2917 BOOL status;
2918 WIN32_FIND_DATAW fd;
2919 HANDLE hff;
2920 WCHAR input[MAX_PATH];
2921 WCHAR output[MAX_PATH];
2922 WCHAR drive[10];
2923 WCHAR dir[MAX_PATH];
2924 WCHAR fname[MAX_PATH];
2925 WCHAR ext[MAX_PATH];
2927 if (param1[0] == 0x00) {
2928 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2929 return;
2932 /* If no destination supplied, assume current directory */
2933 if (param2[0] == 0x00) {
2934 lstrcpyW(param2, L".");
2937 /* If 2nd parm is directory, then use original filename */
2938 /* Convert partial path to full path */
2939 GetFullPathNameW(param1, ARRAY_SIZE(input), input, NULL);
2940 GetFullPathNameW(param2, ARRAY_SIZE(output), output, NULL);
2941 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2942 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2944 /* Split into components */
2945 _wsplitpath(input, drive, dir, fname, ext);
2947 hff = FindFirstFileW(input, &fd);
2948 if (hff == INVALID_HANDLE_VALUE)
2949 return;
2951 do {
2952 WCHAR dest[MAX_PATH];
2953 WCHAR src[MAX_PATH];
2954 DWORD attribs;
2955 BOOL ok = TRUE;
2956 DWORD flags = 0;
2958 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2960 /* Build src & dest name */
2961 lstrcpyW(src, drive);
2962 lstrcatW(src, dir);
2964 /* See if dest is an existing directory */
2965 attribs = GetFileAttributesW(output);
2966 if (attribs != INVALID_FILE_ATTRIBUTES &&
2967 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2968 lstrcpyW(dest, output);
2969 lstrcatW(dest, L"\\");
2970 lstrcatW(dest, fd.cFileName);
2971 } else {
2972 lstrcpyW(dest, output);
2975 lstrcatW(src, fd.cFileName);
2977 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
2978 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
2980 /* If destination exists, prompt unless /Y supplied */
2981 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
2982 BOOL force = FALSE;
2983 WCHAR copycmd[MAXSTRING];
2984 DWORD len;
2986 /* Default whether automatic overwriting is on. If we are interactive then
2987 we prompt by default, otherwise we overwrite by default
2988 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
2989 if (wcsstr(quals, L"/-Y"))
2990 force = FALSE;
2991 else if (wcsstr(quals, L"/Y"))
2992 force = TRUE;
2993 else {
2994 /* By default, we will force the overwrite in batch mode and ask for
2995 * confirmation in interactive mode. */
2996 force = !interactive;
2997 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
2998 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
2999 * default behavior. */
3000 len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
3001 if (len && len < ARRAY_SIZE(copycmd)) {
3002 if (!lstrcmpiW(copycmd, L"/Y"))
3003 force = TRUE;
3004 else if (!lstrcmpiW(copycmd, L"/-Y"))
3005 force = FALSE;
3009 /* Prompt if overwriting */
3010 if (!force) {
3011 WCHAR* question;
3013 /* Ask for confirmation */
3014 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
3015 ok = WCMD_ask_confirm(question, FALSE, NULL);
3016 LocalFree(question);
3019 if (ok)
3020 flags |= MOVEFILE_REPLACE_EXISTING;
3023 if (ok) {
3024 status = MoveFileExW(src, dest, flags);
3025 } else {
3026 status = TRUE;
3029 if (!status) {
3030 WCMD_print_error ();
3031 errorlevel = 1;
3033 } while (FindNextFileW(hff, &fd) != 0);
3035 FindClose(hff);
3038 /****************************************************************************
3039 * WCMD_pause
3041 * Suspend execution of a batch script until a key is typed
3044 void WCMD_pause (void)
3046 DWORD oldmode;
3047 BOOL have_console;
3048 DWORD count;
3049 WCHAR key;
3050 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
3052 have_console = GetConsoleMode(hIn, &oldmode);
3053 if (have_console)
3054 SetConsoleMode(hIn, 0);
3056 WCMD_output_asis(anykey);
3057 WCMD_ReadFile(hIn, &key, 1, &count);
3058 if (have_console)
3059 SetConsoleMode(hIn, oldmode);
3062 /****************************************************************************
3063 * WCMD_remove_dir
3065 * Delete a directory.
3068 void WCMD_remove_dir (WCHAR *args) {
3070 int argno = 0;
3071 int argsProcessed = 0;
3072 WCHAR *argN = args;
3074 /* Loop through all args */
3075 while (argN) {
3076 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3077 if (argN && argN[0] != '/') {
3078 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
3079 wine_dbgstr_w(quals));
3080 argsProcessed++;
3082 /* If subdirectory search not supplied, just try to remove
3083 and report error if it fails (eg if it contains a file) */
3084 if (wcsstr(quals, L"/S") == NULL) {
3085 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
3087 /* Otherwise use ShFileOp to recursively remove a directory */
3088 } else {
3090 SHFILEOPSTRUCTW lpDir;
3092 /* Ask first */
3093 if (wcsstr(quals, L"/Q") == NULL) {
3094 BOOL ok;
3095 WCHAR question[MAXSTRING];
3097 /* Ask for confirmation */
3098 wsprintfW(question, L"%s ", thisArg);
3099 ok = WCMD_ask_confirm(question, TRUE, NULL);
3101 /* Abort if answer is 'N' */
3102 if (!ok) return;
3105 /* Do the delete */
3106 lpDir.hwnd = NULL;
3107 lpDir.pTo = NULL;
3108 lpDir.pFrom = thisArg;
3109 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
3110 lpDir.wFunc = FO_DELETE;
3112 /* SHFileOperationW needs file list with a double null termination */
3113 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3115 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3120 /* Handle no valid args */
3121 if (argsProcessed == 0) {
3122 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3123 return;
3128 /****************************************************************************
3129 * WCMD_rename
3131 * Rename a file.
3134 void WCMD_rename (void)
3136 BOOL status;
3137 HANDLE hff;
3138 WIN32_FIND_DATAW fd;
3139 WCHAR input[MAX_PATH];
3140 WCHAR *dotDst = NULL;
3141 WCHAR drive[10];
3142 WCHAR dir[MAX_PATH];
3143 WCHAR fname[MAX_PATH];
3144 WCHAR ext[MAX_PATH];
3146 errorlevel = 0;
3148 /* Must be at least two args */
3149 if (param1[0] == 0x00 || param2[0] == 0x00) {
3150 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3151 errorlevel = 1;
3152 return;
3155 /* Destination cannot contain a drive letter or directory separator */
3156 if ((wcschr(param2,':') != NULL) || (wcschr(param2,'\\') != NULL)) {
3157 SetLastError(ERROR_INVALID_PARAMETER);
3158 WCMD_print_error();
3159 errorlevel = 1;
3160 return;
3163 /* Convert partial path to full path */
3164 GetFullPathNameW(param1, ARRAY_SIZE(input), input, NULL);
3165 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3166 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3167 dotDst = wcschr(param2, '.');
3169 /* Split into components */
3170 _wsplitpath(input, drive, dir, fname, ext);
3172 hff = FindFirstFileW(input, &fd);
3173 if (hff == INVALID_HANDLE_VALUE)
3174 return;
3176 do {
3177 WCHAR dest[MAX_PATH];
3178 WCHAR src[MAX_PATH];
3179 WCHAR *dotSrc = NULL;
3180 int dirLen;
3182 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3184 /* FIXME: If dest name or extension is *, replace with filename/ext
3185 part otherwise use supplied name. This supports:
3186 ren *.fred *.jim
3187 ren jim.* fred.* etc
3188 However, windows has a more complex algorithm supporting eg
3189 ?'s and *'s mid name */
3190 dotSrc = wcschr(fd.cFileName, '.');
3192 /* Build src & dest name */
3193 lstrcpyW(src, drive);
3194 lstrcatW(src, dir);
3195 lstrcpyW(dest, src);
3196 dirLen = lstrlenW(src);
3197 lstrcatW(src, fd.cFileName);
3199 /* Build name */
3200 if (param2[0] == '*') {
3201 lstrcatW(dest, fd.cFileName);
3202 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3203 } else {
3204 lstrcatW(dest, param2);
3205 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3208 /* Build Extension */
3209 if (dotDst && (*(dotDst+1)=='*')) {
3210 if (dotSrc) lstrcatW(dest, dotSrc);
3211 } else if (dotDst) {
3212 lstrcatW(dest, dotDst);
3215 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3216 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3218 status = MoveFileW(src, dest);
3220 if (!status) {
3221 WCMD_print_error ();
3222 errorlevel = 1;
3224 } while (FindNextFileW(hff, &fd) != 0);
3226 FindClose(hff);
3229 /*****************************************************************************
3230 * WCMD_dupenv
3232 * Make a copy of the environment.
3234 static WCHAR *WCMD_dupenv( const WCHAR *env )
3236 WCHAR *env_copy;
3237 int len;
3239 if( !env )
3240 return NULL;
3242 len = 0;
3243 while ( env[len] )
3244 len += (lstrlenW(&env[len]) + 1);
3246 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3247 if (!env_copy)
3249 WINE_ERR("out of memory\n");
3250 return env_copy;
3252 memcpy (env_copy, env, len*sizeof (WCHAR));
3253 env_copy[len] = 0;
3255 return env_copy;
3258 /*****************************************************************************
3259 * WCMD_setlocal
3261 * setlocal pushes the environment onto a stack
3262 * Save the environment as unicode so we don't screw anything up.
3264 void WCMD_setlocal (const WCHAR *s) {
3265 WCHAR *env;
3266 struct env_stack *env_copy;
3267 WCHAR cwd[MAX_PATH];
3268 BOOL newdelay;
3270 /* setlocal does nothing outside of batch programs */
3271 if (!context) return;
3273 /* DISABLEEXTENSIONS ignored */
3275 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3276 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3277 if (!wcsicmp(param1, L"ENABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"ENABLEDELAYEDEXPANSION")) {
3278 newdelay = TRUE;
3279 } else if (!wcsicmp(param1, L"DISABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"DISABLEDELAYEDEXPANSION")) {
3280 newdelay = FALSE;
3281 } else {
3282 newdelay = delayedsubst;
3284 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3286 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3287 if( !env_copy )
3289 WINE_ERR ("out of memory\n");
3290 return;
3293 env = GetEnvironmentStringsW ();
3294 env_copy->strings = WCMD_dupenv (env);
3295 if (env_copy->strings)
3297 env_copy->batchhandle = context->h;
3298 env_copy->next = saved_environment;
3299 env_copy->delayedsubst = delayedsubst;
3300 delayedsubst = newdelay;
3301 saved_environment = env_copy;
3303 /* Save the current drive letter */
3304 GetCurrentDirectoryW(MAX_PATH, cwd);
3305 env_copy->u.cwd = cwd[0];
3307 else
3308 LocalFree (env_copy);
3310 FreeEnvironmentStringsW (env);
3314 /*****************************************************************************
3315 * WCMD_endlocal
3317 * endlocal pops the environment off a stack
3318 * Note: When searching for '=', search from WCHAR position 1, to handle
3319 * special internal environment variables =C:, =D: etc
3321 void WCMD_endlocal (void) {
3322 WCHAR *env, *old, *p;
3323 struct env_stack *temp;
3324 int len, n;
3326 /* setlocal does nothing outside of batch programs */
3327 if (!context) return;
3329 /* setlocal needs a saved environment from within the same context (batch
3330 program) as it was saved in */
3331 if (!saved_environment || saved_environment->batchhandle != context->h)
3332 return;
3334 /* pop the old environment from the stack */
3335 temp = saved_environment;
3336 saved_environment = temp->next;
3338 /* delete the current environment, totally */
3339 env = GetEnvironmentStringsW ();
3340 old = WCMD_dupenv (env);
3341 len = 0;
3342 while (old[len]) {
3343 n = lstrlenW(&old[len]) + 1;
3344 p = wcschr(&old[len] + 1, '=');
3345 if (p)
3347 *p++ = 0;
3348 SetEnvironmentVariableW (&old[len], NULL);
3350 len += n;
3352 LocalFree (old);
3353 FreeEnvironmentStringsW (env);
3355 /* restore old environment */
3356 env = temp->strings;
3357 len = 0;
3358 delayedsubst = temp->delayedsubst;
3359 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3360 while (env[len]) {
3361 n = lstrlenW(&env[len]) + 1;
3362 p = wcschr(&env[len] + 1, '=');
3363 if (p)
3365 *p++ = 0;
3366 SetEnvironmentVariableW (&env[len], p);
3368 len += n;
3371 /* Restore current drive letter */
3372 if (IsCharAlphaW(temp->u.cwd)) {
3373 WCHAR envvar[4];
3374 WCHAR cwd[MAX_PATH];
3376 wsprintfW(envvar, L"=%c:", temp->u.cwd);
3377 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3378 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3379 SetCurrentDirectoryW(cwd);
3383 LocalFree (env);
3384 LocalFree (temp);
3387 /*****************************************************************************
3388 * WCMD_setshow_default
3390 * Set/Show the current default directory
3393 void WCMD_setshow_default (const WCHAR *args) {
3395 BOOL status;
3396 WCHAR string[1024];
3397 WCHAR cwd[1024];
3398 WCHAR *pos;
3399 WIN32_FIND_DATAW fd;
3400 HANDLE hff;
3402 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3404 /* Skip /D and trailing whitespace if on the front of the command line */
3405 if (lstrlenW(args) >= 2 &&
3406 CompareStringW(LOCALE_USER_DEFAULT,
3407 NORM_IGNORECASE | SORT_STRINGSORT,
3408 args, 2, L"/D", -1) == CSTR_EQUAL) {
3409 args += 2;
3410 while (*args && (*args==' ' || *args=='\t'))
3411 args++;
3414 GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
3416 if (!*args) {
3417 lstrcatW(cwd, L"\r\n");
3418 WCMD_output_asis (cwd);
3420 else {
3421 /* Remove any double quotes, which may be in the
3422 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3423 pos = string;
3424 while (*args) {
3425 if (*args != '"') *pos++ = *args;
3426 args++;
3428 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3429 pos--;
3430 *pos = 0x00;
3432 /* Search for appropriate directory */
3433 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3434 hff = FindFirstFileW(string, &fd);
3435 if (hff != INVALID_HANDLE_VALUE) {
3436 do {
3437 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3438 WCHAR fpath[MAX_PATH];
3439 WCHAR drive[10];
3440 WCHAR dir[MAX_PATH];
3441 WCHAR fname[MAX_PATH];
3442 WCHAR ext[MAX_PATH];
3444 /* Convert path into actual directory spec */
3445 GetFullPathNameW(string, ARRAY_SIZE(fpath), fpath, NULL);
3446 _wsplitpath(fpath, drive, dir, fname, ext);
3448 /* Rebuild path */
3449 wsprintfW(string, L"%s%s%s", drive, dir, fd.cFileName);
3450 break;
3452 } while (FindNextFileW(hff, &fd) != 0);
3453 FindClose(hff);
3456 /* Change to that directory */
3457 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3459 status = SetCurrentDirectoryW(string);
3460 if (!status) {
3461 errorlevel = 1;
3462 WCMD_print_error ();
3463 return;
3464 } else {
3466 /* Save away the actual new directory, to store as current location */
3467 GetCurrentDirectoryW(ARRAY_SIZE(string), string);
3469 /* Restore old directory if drive letter would change, and
3470 CD x:\directory /D (or pushd c:\directory) not supplied */
3471 if ((wcsstr(quals, L"/D") == NULL) &&
3472 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
3473 SetCurrentDirectoryW(cwd);
3477 /* Set special =C: type environment variable, for drive letter of
3478 change of directory, even if path was restored due to missing
3479 /D (allows changing drive letter when not resident on that
3480 drive */
3481 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3482 WCHAR env[4];
3483 lstrcpyW(env, L"=");
3484 memcpy(env+1, string, 2 * sizeof(WCHAR));
3485 env[3] = 0x00;
3486 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3487 SetEnvironmentVariableW(env, string);
3491 return;
3494 /****************************************************************************
3495 * WCMD_setshow_date
3497 * Set/Show the system date
3498 * FIXME: Can't change date yet
3501 void WCMD_setshow_date (void) {
3503 WCHAR curdate[64], buffer[64];
3504 DWORD count;
3506 if (!*param1) {
3507 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, curdate, ARRAY_SIZE(curdate))) {
3508 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3509 if (wcsstr(quals, L"/T") == NULL) {
3510 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3511 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
3512 if (count > 2) {
3513 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3517 else WCMD_print_error ();
3519 else {
3520 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3524 /****************************************************************************
3525 * WCMD_compare
3526 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3527 * the equals sign.
3529 static int __cdecl WCMD_compare( const void *a, const void *b )
3531 int r;
3532 const WCHAR * const *str_a = a, * const *str_b = b;
3533 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3534 *str_a, wcscspn(*str_a, L"="), *str_b, wcscspn(*str_b, L"=") );
3535 if( r == CSTR_LESS_THAN ) return -1;
3536 if( r == CSTR_GREATER_THAN ) return 1;
3537 return 0;
3540 /****************************************************************************
3541 * WCMD_setshow_sortenv
3543 * sort variables into order for display
3544 * Optionally only display those who start with a stub
3545 * returns the count displayed
3547 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3549 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3550 const WCHAR **str;
3552 if (stub) stublen = lstrlenW(stub);
3554 /* count the number of strings, and the total length */
3555 while ( s[len] ) {
3556 len += (lstrlenW(&s[len]) + 1);
3557 count++;
3560 /* add the strings to an array */
3561 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3562 if( !str )
3563 return 0;
3564 str[0] = s;
3565 for( i=1; i<count; i++ )
3566 str[i] = str[i-1] + lstrlenW(str[i-1]) + 1;
3568 /* sort the array */
3569 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3571 /* print it */
3572 for( i=0; i<count; i++ ) {
3573 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3574 NORM_IGNORECASE | SORT_STRINGSORT,
3575 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3576 /* Don't display special internal variables */
3577 if (str[i][0] != '=') {
3578 WCMD_output_asis(str[i]);
3579 WCMD_output_asis(L"\r\n");
3580 displayedcount++;
3585 LocalFree( str );
3586 return displayedcount;
3589 /****************************************************************************
3590 * WCMD_getprecedence
3591 * Return the precedence of a particular operator
3593 static int WCMD_getprecedence(const WCHAR in)
3595 switch (in) {
3596 case '!':
3597 case '~':
3598 case OP_POSITIVE:
3599 case OP_NEGATIVE:
3600 return 8;
3601 case '*':
3602 case '/':
3603 case '%':
3604 return 7;
3605 case '+':
3606 case '-':
3607 return 6;
3608 case '<':
3609 case '>':
3610 return 5;
3611 case '&':
3612 return 4;
3613 case '^':
3614 return 3;
3615 case '|':
3616 return 2;
3617 case '=':
3618 case OP_ASSSIGNMUL:
3619 case OP_ASSSIGNDIV:
3620 case OP_ASSSIGNMOD:
3621 case OP_ASSSIGNADD:
3622 case OP_ASSSIGNSUB:
3623 case OP_ASSSIGNAND:
3624 case OP_ASSSIGNNOT:
3625 case OP_ASSSIGNOR:
3626 case OP_ASSSIGNSHL:
3627 case OP_ASSSIGNSHR:
3628 return 1;
3629 default:
3630 return 0;
3634 /****************************************************************************
3635 * WCMD_pushnumber
3636 * Push either a number or name (environment variable) onto the supplied
3637 * stack
3639 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3640 VARSTACK *thisstack = heap_xalloc(sizeof(VARSTACK));
3641 thisstack->isnum = (var == NULL);
3642 if (var) {
3643 thisstack->variable = var;
3644 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3645 } else {
3646 thisstack->value = num;
3647 WINE_TRACE("Pushed number %d\n", num);
3649 thisstack->next = *varstack;
3650 *varstack = thisstack;
3653 /****************************************************************************
3654 * WCMD_peeknumber
3655 * Returns the value of the top number or environment variable on the stack
3656 * and leaves the item on the stack.
3658 static int WCMD_peeknumber(VARSTACK **varstack) {
3659 int result = 0;
3660 VARSTACK *thisvar;
3662 if (varstack) {
3663 thisvar = *varstack;
3664 if (!thisvar->isnum) {
3665 WCHAR tmpstr[MAXSTRING];
3666 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3667 result = wcstol(tmpstr,NULL,0);
3669 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3670 } else {
3671 result = thisvar->value;
3674 WINE_TRACE("Peeked number %d\n", result);
3675 return result;
3678 /****************************************************************************
3679 * WCMD_popnumber
3680 * Returns the value of the top number or environment variable on the stack
3681 * and removes the item from the stack.
3683 static int WCMD_popnumber(VARSTACK **varstack) {
3684 int result = 0;
3685 VARSTACK *thisvar;
3687 if (varstack) {
3688 thisvar = *varstack;
3689 result = WCMD_peeknumber(varstack);
3690 if (!thisvar->isnum) heap_free(thisvar->variable);
3691 *varstack = thisvar->next;
3692 heap_free(thisvar);
3694 WINE_TRACE("Popped number %d\n", result);
3695 return result;
3698 /****************************************************************************
3699 * WCMD_pushoperator
3700 * Push an operator onto the supplied stack
3702 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3703 OPSTACK *thisstack = heap_xalloc(sizeof(OPSTACK));
3704 thisstack->precedence = precedence;
3705 thisstack->op = op;
3706 thisstack->next = *opstack;
3707 WINE_TRACE("Pushed operator %c\n", op);
3708 *opstack = thisstack;
3711 /****************************************************************************
3712 * WCMD_popoperator
3713 * Returns the operator from the top of the stack and removes the item from
3714 * the stack.
3716 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3717 WCHAR result = 0;
3718 OPSTACK *thisop;
3720 if (opstack) {
3721 thisop = *opstack;
3722 result = thisop->op;
3723 *opstack = thisop->next;
3724 heap_free(thisop);
3726 WINE_TRACE("Popped operator %c\n", result);
3727 return result;
3730 /****************************************************************************
3731 * WCMD_reduce
3732 * Actions the top operator on the stack against the first and sometimes
3733 * second value on the variable stack, and pushes the result
3734 * Returns non-zero on error.
3736 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3737 WCHAR thisop;
3738 int var1,var2;
3739 int rc = 0;
3741 if (!*opstack || !*varstack) {
3742 WINE_TRACE("No operators for the reduce\n");
3743 return WCMD_NOOPERATOR;
3746 /* Remove the top operator */
3747 thisop = WCMD_popoperator(opstack);
3748 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
3750 /* One variable operators */
3751 var1 = WCMD_popnumber(varstack);
3752 switch (thisop) {
3753 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3754 break;
3755 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3756 break;
3757 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3758 break;
3759 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3760 break;
3763 /* Two variable operators */
3764 if (!*varstack) {
3765 WINE_TRACE("No operands left for the reduce?\n");
3766 return WCMD_NOOPERAND;
3768 switch (thisop) {
3769 case '!':
3770 case '~':
3771 case OP_POSITIVE:
3772 case OP_NEGATIVE:
3773 break; /* Handled above */
3774 case '*': var2 = WCMD_popnumber(varstack);
3775 WCMD_pushnumber(NULL, var2*var1, varstack);
3776 break;
3777 case '/': var2 = WCMD_popnumber(varstack);
3778 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3779 WCMD_pushnumber(NULL, var2/var1, varstack);
3780 break;
3781 case '+': var2 = WCMD_popnumber(varstack);
3782 WCMD_pushnumber(NULL, var2+var1, varstack);
3783 break;
3784 case '-': var2 = WCMD_popnumber(varstack);
3785 WCMD_pushnumber(NULL, var2-var1, varstack);
3786 break;
3787 case '&': var2 = WCMD_popnumber(varstack);
3788 WCMD_pushnumber(NULL, var2&var1, varstack);
3789 break;
3790 case '%': var2 = WCMD_popnumber(varstack);
3791 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3792 WCMD_pushnumber(NULL, var2%var1, varstack);
3793 break;
3794 case '^': var2 = WCMD_popnumber(varstack);
3795 WCMD_pushnumber(NULL, var2^var1, varstack);
3796 break;
3797 case '<': var2 = WCMD_popnumber(varstack);
3798 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3799 which differs from the compiler (for example gcc) so being explicit. */
3800 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3801 WCMD_pushnumber(NULL, 0, varstack);
3802 } else {
3803 WCMD_pushnumber(NULL, var2<<var1, varstack);
3805 break;
3806 case '>': var2 = WCMD_popnumber(varstack);
3807 WCMD_pushnumber(NULL, var2>>var1, varstack);
3808 break;
3809 case '|': var2 = WCMD_popnumber(varstack);
3810 WCMD_pushnumber(NULL, var2|var1, varstack);
3811 break;
3813 case OP_ASSSIGNMUL:
3814 case OP_ASSSIGNDIV:
3815 case OP_ASSSIGNMOD:
3816 case OP_ASSSIGNADD:
3817 case OP_ASSSIGNSUB:
3818 case OP_ASSSIGNAND:
3819 case OP_ASSSIGNNOT:
3820 case OP_ASSSIGNOR:
3821 case OP_ASSSIGNSHL:
3822 case OP_ASSSIGNSHR:
3824 int i = 0;
3826 /* The left of an equals must be one variable */
3827 if (!(*varstack) || (*varstack)->isnum) {
3828 return WCMD_NOOPERAND;
3831 /* Make the number stack grow by inserting the value of the variable */
3832 var2 = WCMD_peeknumber(varstack);
3833 WCMD_pushnumber(NULL, var2, varstack);
3834 WCMD_pushnumber(NULL, var1, varstack);
3836 /* Make the operand stack grow by pushing the assign operator plus the
3837 operator to perform */
3838 while (calcassignments[i].op != ' ' &&
3839 calcassignments[i].calculatedop != thisop) {
3840 i++;
3842 if (calcassignments[i].calculatedop == ' ') {
3843 WINE_ERR("Unexpected operator %c\n", thisop);
3844 return WCMD_NOOPERATOR;
3846 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3847 WCMD_pushoperator(calcassignments[i].op,
3848 WCMD_getprecedence(calcassignments[i].op), opstack);
3849 break;
3852 case '=':
3854 WCHAR result[MAXSTRING];
3856 /* Build the result, then push it onto the stack */
3857 swprintf(result, ARRAY_SIZE(result), L"%d", var1);
3858 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3859 wine_dbgstr_w(result));
3860 SetEnvironmentVariableW((*varstack)->variable, result);
3861 var2 = WCMD_popnumber(varstack);
3862 WCMD_pushnumber(NULL, var1, varstack);
3863 break;
3866 default: WINE_ERR("Unrecognized operator %c\n", thisop);
3869 return rc;
3873 /****************************************************************************
3874 * WCMD_handleExpression
3875 * Handles an expression provided to set /a - If it finds brackets, it uses
3876 * recursion to process the parts in brackets.
3878 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3880 static const WCHAR mathDelims[] = L" \t()!~-*/%+<>&^|=,";
3881 int rc = 0;
3882 WCHAR *pos;
3883 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3884 OPSTACK *opstackhead = NULL;
3885 VARSTACK *varstackhead = NULL;
3886 WCHAR foundhalf = 0;
3888 /* Initialize */
3889 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3890 pos = *expr;
3892 /* Iterate through until whole expression is processed */
3893 while (pos && *pos) {
3894 BOOL treatasnumber;
3896 /* Skip whitespace to get to the next character to process*/
3897 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3898 if (!*pos) goto exprreturn;
3900 /* If we have found anything other than an operator then it's a number/variable */
3901 if (wcschr(mathDelims, *pos) == NULL) {
3902 WCHAR *parmstart, *parm, *dupparm;
3903 WCHAR *nextpos;
3905 /* Cannot have an expression with var/number twice, without an operator
3906 in-between, nor or number following a half constructed << or >> operator */
3907 if (lastwasnumber || foundhalf) {
3908 rc = WCMD_NOOPERATOR;
3909 goto exprerrorreturn;
3911 lastwasnumber = TRUE;
3913 if (iswdigit(*pos)) {
3914 /* For a number - just push it onto the stack */
3915 int num = wcstoul(pos, &nextpos, 0);
3916 WCMD_pushnumber(NULL, num, &varstackhead);
3917 pos = nextpos;
3919 /* Verify the number was validly formed */
3920 if (*nextpos && (wcschr(mathDelims, *nextpos) == NULL)) {
3921 rc = WCMD_BADHEXOCT;
3922 goto exprerrorreturn;
3924 } else {
3926 /* For a variable - just push it onto the stack */
3927 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3928 dupparm = heap_strdupW(parm);
3929 WCMD_pushnumber(dupparm, 0, &varstackhead);
3930 pos = parmstart + lstrlenW(dupparm);
3932 continue;
3935 /* We have found an operator. Some operators are one character, some two, and the minus
3936 and plus signs need special processing as they can be either operators or just influence
3937 the parameter which follows them */
3938 if (foundhalf && (*pos != foundhalf)) {
3939 /* Badly constructed operator pair */
3940 rc = WCMD_NOOPERATOR;
3941 goto exprerrorreturn;
3944 treatasnumber = FALSE; /* We are processing an operand */
3945 switch (*pos) {
3947 /* > and < are special as they are double character operators (and spaces can be between them!)
3948 If we see these for the first time, set a flag, and second time around we continue.
3949 Note these double character operators are stored as just one of the characters on the stack */
3950 case '>':
3951 case '<': if (!foundhalf) {
3952 foundhalf = *pos;
3953 pos++;
3954 break;
3956 /* We have found the rest, so clear up the knowledge of the half completed part and
3957 drop through to normal operator processing */
3958 foundhalf = 0;
3959 /* drop through */
3961 case '=': if (*pos=='=') {
3962 /* = is special cased as if the last was an operator then we may have e.g. += or
3963 *= etc which we need to handle by replacing the operator that is on the stack
3964 with a calculated assignment equivalent */
3965 if (!lastwasnumber && opstackhead) {
3966 int i = 0;
3967 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3968 i++;
3970 if (calcassignments[i].op == ' ') {
3971 rc = WCMD_NOOPERAND;
3972 goto exprerrorreturn;
3973 } else {
3974 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
3975 when the general operator handling happens further down. */
3976 *pos = calcassignments[i].calculatedop;
3977 WCMD_popoperator(&opstackhead);
3981 /* Drop though */
3983 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
3984 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
3985 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
3986 /* drop through */
3987 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
3988 /* drop through */
3990 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
3991 case '!': /* drop through */
3992 case '~': /* drop through */
3993 case '/': /* drop through */
3994 case '%': /* drop through */
3995 case '&': /* drop through */
3996 case '^': /* drop through */
3997 case '*': /* drop through */
3998 case '|':
3999 /* General code for handling most of the operators - look at the
4000 precedence of the top item on the stack, and see if we need to
4001 action the stack before we push something else onto it. */
4003 int precedence = WCMD_getprecedence(*pos);
4004 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
4005 precedence, !opstackhead?-1:opstackhead->precedence);
4007 /* In general, for things with the same precedence, reduce immediately
4008 except for assignments and unary operators which do not */
4009 while (!rc && opstackhead &&
4010 ((opstackhead->precedence > precedence) ||
4011 ((opstackhead->precedence == precedence) &&
4012 (precedence != 1) && (precedence != 8)))) {
4013 rc = WCMD_reduce(&opstackhead, &varstackhead);
4015 if (rc) goto exprerrorreturn;
4016 WCMD_pushoperator(*pos, precedence, &opstackhead);
4017 pos++;
4018 break;
4021 /* comma means start a new expression, ie calculate what we have */
4022 case ',':
4024 int prevresult = -1;
4025 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
4026 while (!rc && opstackhead) {
4027 rc = WCMD_reduce(&opstackhead, &varstackhead);
4029 if (rc) goto exprerrorreturn;
4030 /* If we have anything other than one number left, error
4031 otherwise throw the number away */
4032 if (!varstackhead || varstackhead->next) {
4033 rc = WCMD_NOOPERATOR;
4034 goto exprerrorreturn;
4036 prevresult = WCMD_popnumber(&varstackhead);
4037 WINE_TRACE("Expression resolved to %d\n", prevresult);
4038 heap_free(varstackhead);
4039 varstackhead = NULL;
4040 pos++;
4041 break;
4044 /* Open bracket - use iteration to parse the inner expression, then continue */
4045 case '(' : {
4046 int exprresult = 0;
4047 pos++;
4048 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
4049 if (rc) goto exprerrorreturn;
4050 WCMD_pushnumber(NULL, exprresult, &varstackhead);
4051 break;
4054 /* Close bracket - we have finished this depth, calculate and return */
4055 case ')' : {
4056 pos++;
4057 treatasnumber = TRUE; /* Things in brackets result in a number */
4058 if (depth == 0) {
4059 rc = WCMD_BADPAREN;
4060 goto exprerrorreturn;
4062 goto exprreturn;
4065 default:
4066 WINE_ERR("Unrecognized operator %c\n", *pos);
4067 pos++;
4069 lastwasnumber = treatasnumber;
4072 exprreturn:
4073 *expr = pos;
4075 /* We need to reduce until we have a single number (or variable) on the
4076 stack and set the return value to that */
4077 while (!rc && opstackhead) {
4078 rc = WCMD_reduce(&opstackhead, &varstackhead);
4080 if (rc) goto exprerrorreturn;
4082 /* If we have anything other than one number left, error
4083 otherwise throw the number away */
4084 if (!varstackhead || varstackhead->next) {
4085 rc = WCMD_NOOPERATOR;
4086 goto exprerrorreturn;
4089 /* Now get the number (and convert if it's just a variable name) */
4090 *ret = WCMD_popnumber(&varstackhead);
4092 exprerrorreturn:
4093 /* Free all remaining memory */
4094 while (opstackhead) WCMD_popoperator(&opstackhead);
4095 while (varstackhead) WCMD_popnumber(&varstackhead);
4097 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
4098 return rc;
4101 /****************************************************************************
4102 * WCMD_setshow_env
4104 * Set/Show the environment variables
4107 void WCMD_setshow_env (WCHAR *s) {
4109 LPVOID env;
4110 WCHAR *p;
4111 BOOL status;
4112 WCHAR string[MAXSTRING];
4114 if (param1[0] == 0x00 && quals[0] == 0x00) {
4115 env = GetEnvironmentStringsW();
4116 WCMD_setshow_sortenv( env, NULL );
4117 return;
4120 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4121 if (CompareStringW(LOCALE_USER_DEFAULT,
4122 NORM_IGNORECASE | SORT_STRINGSORT,
4123 s, 2, L"/P", -1) == CSTR_EQUAL) {
4124 DWORD count;
4126 s += 2;
4127 while (*s && (*s==' ' || *s=='\t')) s++;
4128 /* set /P "var=value"jim ignores anything after the last quote */
4129 if (*s=='\"') {
4130 WCHAR *lastquote;
4131 lastquote = WCMD_strip_quotes(s);
4132 if (lastquote) *lastquote = 0x00;
4133 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4136 /* If no parameter, or no '=' sign, return an error */
4137 if (!(*s) || ((p = wcschr (s, '=')) == NULL )) {
4138 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4139 return;
4142 /* Output the prompt */
4143 *p++ = '\0';
4144 if (*p) WCMD_output_asis(p);
4146 /* Read the reply */
4147 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
4148 if (count > 1) {
4149 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4150 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4151 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4152 wine_dbgstr_w(string));
4153 SetEnvironmentVariableW(s, string);
4156 /* See if /A supplied, and if so calculate the results of all the expressions */
4157 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4158 NORM_IGNORECASE | SORT_STRINGSORT,
4159 s, 2, L"/A", -1) == CSTR_EQUAL) {
4160 /* /A supplied, so evaluate expressions and set variables appropriately */
4161 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4162 /* of the final computation */
4163 int result = 0;
4164 int rc = 0;
4165 WCHAR *thisexpr;
4166 WCHAR *src,*dst;
4168 /* Remove all quotes before doing any calculations */
4169 thisexpr = heap_xalloc((lstrlenW(s+2)+1) * sizeof(WCHAR));
4170 src = s+2;
4171 dst = thisexpr;
4172 while (*src) {
4173 if (*src != '"') *dst++ = *src;
4174 src++;
4176 *dst = 0;
4178 /* Now calculate the results of the expression */
4179 src = thisexpr;
4180 rc = WCMD_handleExpression(&src, &result, 0);
4181 heap_free(thisexpr);
4183 /* If parsing failed, issue the error message */
4184 if (rc > 0) {
4185 WCMD_output_stderr(WCMD_LoadMessage(rc));
4186 return;
4189 /* If we have no context (interactive or cmd.exe /c) print the final result */
4190 if (!context) {
4191 swprintf(string, ARRAY_SIZE(string), L"%d", result);
4192 WCMD_output_asis(string);
4195 } else {
4196 DWORD gle;
4198 /* set "var=value"jim ignores anything after the last quote */
4199 if (*s=='\"') {
4200 WCHAR *lastquote;
4201 lastquote = WCMD_strip_quotes(s);
4202 if (lastquote) *lastquote = 0x00;
4203 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4206 p = wcschr (s, '=');
4207 if (p == NULL) {
4208 env = GetEnvironmentStringsW();
4209 if (WCMD_setshow_sortenv( env, s ) == 0) {
4210 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4211 errorlevel = 1;
4213 return;
4215 *p++ = '\0';
4217 if (!*p) p = NULL;
4218 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4219 wine_dbgstr_w(p));
4220 status = SetEnvironmentVariableW(s, p);
4221 gle = GetLastError();
4222 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4223 errorlevel = 1;
4224 } else if (!status) WCMD_print_error();
4225 else errorlevel = 0;
4229 /****************************************************************************
4230 * WCMD_setshow_path
4232 * Set/Show the path environment variable
4235 void WCMD_setshow_path (const WCHAR *args) {
4237 WCHAR string[1024];
4238 DWORD status;
4240 if (!*param1 && !*param2) {
4241 status = GetEnvironmentVariableW(L"PATH", string, ARRAY_SIZE(string));
4242 if (status != 0) {
4243 WCMD_output_asis(L"PATH=");
4244 WCMD_output_asis ( string);
4245 WCMD_output_asis(L"\r\n");
4247 else {
4248 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4251 else {
4252 if (*args == '=') args++; /* Skip leading '=' */
4253 status = SetEnvironmentVariableW(L"PATH", args);
4254 if (!status) WCMD_print_error();
4258 /****************************************************************************
4259 * WCMD_setshow_prompt
4261 * Set or show the command prompt.
4264 void WCMD_setshow_prompt (void) {
4266 WCHAR *s;
4268 if (!*param1) {
4269 SetEnvironmentVariableW(L"PROMPT", NULL);
4271 else {
4272 s = param1;
4273 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4274 if (!*s) {
4275 SetEnvironmentVariableW(L"PROMPT", NULL);
4277 else SetEnvironmentVariableW(L"PROMPT", s);
4281 /****************************************************************************
4282 * WCMD_setshow_time
4284 * Set/Show the system time
4285 * FIXME: Can't change time yet
4288 void WCMD_setshow_time (void) {
4290 WCHAR curtime[64], buffer[64];
4291 DWORD count;
4292 SYSTEMTIME st;
4294 if (!*param1) {
4295 GetLocalTime(&st);
4296 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, curtime, ARRAY_SIZE(curtime))) {
4297 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4298 if (wcsstr(quals, L"/T") == NULL) {
4299 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4300 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
4301 if (count > 2) {
4302 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4306 else WCMD_print_error ();
4308 else {
4309 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4313 /****************************************************************************
4314 * WCMD_shift
4316 * Shift batch parameters.
4317 * Optional /n says where to start shifting (n=0-8)
4320 void WCMD_shift (const WCHAR *args) {
4321 int start;
4323 if (context != NULL) {
4324 WCHAR *pos = wcschr(args, '/');
4325 int i;
4327 if (pos == NULL) {
4328 start = 0;
4329 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4330 start = (*(pos+1) - '0');
4331 } else {
4332 SetLastError(ERROR_INVALID_PARAMETER);
4333 WCMD_print_error();
4334 return;
4337 WINE_TRACE("Shifting variables, starting at %d\n", start);
4338 for (i=start;i<=8;i++) {
4339 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4341 context -> shift_count[9] = context -> shift_count[9] + 1;
4346 /****************************************************************************
4347 * WCMD_start
4349 void WCMD_start(WCHAR *args)
4351 int argno;
4352 int have_title;
4353 WCHAR file[MAX_PATH];
4354 WCHAR *cmdline, *cmdline_params;
4355 STARTUPINFOW st;
4356 PROCESS_INFORMATION pi;
4358 GetWindowsDirectoryW( file, MAX_PATH );
4359 lstrcatW(file, L"\\command\\start.exe");
4360 cmdline = heap_xalloc( (lstrlenW(file) + lstrlenW(args) + 8) * sizeof(WCHAR) );
4361 lstrcpyW( cmdline, file );
4362 lstrcatW(cmdline, L" ");
4363 cmdline_params = cmdline + lstrlenW(cmdline);
4365 /* The start built-in has some special command-line parsing properties
4366 * which will be outlined here.
4368 * both '\t' and ' ' are argument separators
4369 * '/' has a special double role as both separator and switch prefix, e.g.
4371 * > start /low/i
4372 * or
4373 * > start "title"/i
4375 * are valid ways to pass multiple options to start. In the latter case
4376 * '/i' is not a part of the title but parsed as a switch.
4378 * However, '=', ';' and ',' are not separators:
4379 * > start "deus"=ex,machina
4381 * will in fact open a console titled 'deus=ex,machina'
4383 * The title argument parsing code is only interested in quotes themselves,
4384 * it does not respect escaping of any kind and all quotes are dropped
4385 * from the resulting title, therefore:
4387 * > start "\"" hello"/low
4389 * actually opens a console titled '\ hello' with low priorities.
4391 * To not break compatibility with wine programs relying on
4392 * wine's separate 'start.exe', this program's peculiar console
4393 * title parsing is actually implemented in 'cmd.exe' which is the
4394 * application native Windows programs will use to invoke 'start'.
4396 * WCMD_parameter_with_delims will take care of everything for us.
4398 have_title = FALSE;
4399 for (argno=0; ; argno++) {
4400 WCHAR *thisArg, *argN;
4402 argN = NULL;
4403 thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, L" \t/");
4405 /* No more parameters */
4406 if (!argN)
4407 break;
4409 /* Found the title */
4410 if (argN[0] == '"') {
4411 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
4412 have_title = TRUE;
4414 /* Copy all of the cmdline processed */
4415 memcpy(cmdline_params, args, sizeof(WCHAR) * (argN - args));
4416 cmdline_params[argN - args] = '\0';
4418 /* Add quoted title */
4419 lstrcatW(cmdline_params, L"\"\\\"");
4420 lstrcatW(cmdline_params, thisArg);
4421 lstrcatW(cmdline_params, L"\\\"\"");
4423 /* Concatenate remaining command-line */
4424 thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, L" \t/");
4425 lstrcatW(cmdline_params, argN + lstrlenW(thisArg));
4427 break;
4430 /* Skipping a regular argument? */
4431 else if (argN != args && argN[-1] == '/') {
4432 continue;
4434 /* Not an argument nor the title, start of program arguments,
4435 * stop looking for title.
4437 } else
4438 break;
4441 /* build command-line if not built yet */
4442 if (!have_title) {
4443 lstrcatW( cmdline, args );
4446 memset( &st, 0, sizeof(STARTUPINFOW) );
4447 st.cb = sizeof(STARTUPINFOW);
4449 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4451 WaitForSingleObject( pi.hProcess, INFINITE );
4452 GetExitCodeProcess( pi.hProcess, &errorlevel );
4453 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4454 CloseHandle(pi.hProcess);
4455 CloseHandle(pi.hThread);
4457 else
4459 SetLastError(ERROR_FILE_NOT_FOUND);
4460 WCMD_print_error ();
4461 errorlevel = 9009;
4463 heap_free(cmdline);
4466 /****************************************************************************
4467 * WCMD_title
4469 * Set the console title
4471 void WCMD_title (const WCHAR *args) {
4472 SetConsoleTitleW(args);
4475 /****************************************************************************
4476 * WCMD_type
4478 * Copy a file to standard output.
4481 void WCMD_type (WCHAR *args) {
4483 int argno = 0;
4484 WCHAR *argN = args;
4485 BOOL writeHeaders = FALSE;
4487 if (param1[0] == 0x00) {
4488 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4489 return;
4492 if (param2[0] != 0x00) writeHeaders = TRUE;
4494 /* Loop through all args */
4495 errorlevel = 0;
4496 while (argN) {
4497 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4499 HANDLE h;
4500 WCHAR buffer[512];
4501 DWORD count;
4503 if (!argN) break;
4505 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4506 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4507 FILE_ATTRIBUTE_NORMAL, NULL);
4508 if (h == INVALID_HANDLE_VALUE) {
4509 WCMD_print_error ();
4510 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4511 errorlevel = 1;
4512 } else {
4513 if (writeHeaders) {
4514 WCMD_output_stderr(L"\n%1\n\n\n", thisArg);
4516 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer) - 1, &count)) {
4517 if (count == 0) break; /* ReadFile reports success on EOF! */
4518 buffer[count] = 0;
4519 WCMD_output_asis (buffer);
4521 CloseHandle (h);
4526 /****************************************************************************
4527 * WCMD_more
4529 * Output either a file or stdin to screen in pages
4532 void WCMD_more (WCHAR *args) {
4534 int argno = 0;
4535 WCHAR *argN = args;
4536 WCHAR moreStr[100];
4537 WCHAR moreStrPage[100];
4538 WCHAR buffer[512];
4539 DWORD count;
4541 /* Prefix the NLS more with '-- ', then load the text */
4542 errorlevel = 0;
4543 lstrcpyW(moreStr, L"-- ");
4544 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3], ARRAY_SIZE(moreStr)-3);
4546 if (param1[0] == 0x00) {
4548 /* Wine implements pipes via temporary files, and hence stdin is
4549 effectively reading from the file. This means the prompts for
4550 more are satisfied by the next line from the input (file). To
4551 avoid this, ensure stdin is to the console */
4552 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4553 HANDLE hConIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
4554 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4555 FILE_ATTRIBUTE_NORMAL, 0);
4556 WINE_TRACE("No parms - working probably in pipe mode\n");
4557 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4559 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4560 once you get in this bit unless due to a pipe, it's going to end badly... */
4561 wsprintfW(moreStrPage, L"%s --\n", moreStr);
4563 WCMD_enter_paged_mode(moreStrPage);
4564 while (WCMD_ReadFile(hstdin, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4565 if (count == 0) break; /* ReadFile reports success on EOF! */
4566 buffer[count] = 0;
4567 WCMD_output_asis (buffer);
4569 WCMD_leave_paged_mode();
4571 /* Restore stdin to what it was */
4572 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4573 CloseHandle(hConIn);
4575 return;
4576 } else {
4577 BOOL needsPause = FALSE;
4579 /* Loop through all args */
4580 WINE_TRACE("Parms supplied - working through each file\n");
4581 WCMD_enter_paged_mode(moreStrPage);
4583 while (argN) {
4584 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4585 HANDLE h;
4587 if (!argN) break;
4589 if (needsPause) {
4591 /* Wait */
4592 wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, 100);
4593 WCMD_leave_paged_mode();
4594 WCMD_output_asis(moreStrPage);
4595 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
4596 WCMD_enter_paged_mode(moreStrPage);
4600 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4601 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4602 FILE_ATTRIBUTE_NORMAL, NULL);
4603 if (h == INVALID_HANDLE_VALUE) {
4604 WCMD_print_error ();
4605 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4606 errorlevel = 1;
4607 } else {
4608 ULONG64 curPos = 0;
4609 ULONG64 fileLen = 0;
4610 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4612 /* Get the file size */
4613 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4614 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4616 needsPause = TRUE;
4617 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4618 if (count == 0) break; /* ReadFile reports success on EOF! */
4619 buffer[count] = 0;
4620 curPos += count;
4622 /* Update % count (would be used in WCMD_output_asis as prompt) */
4623 wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, (int) min(99, (curPos * 100)/fileLen));
4625 WCMD_output_asis (buffer);
4627 CloseHandle (h);
4631 WCMD_leave_paged_mode();
4635 /****************************************************************************
4636 * WCMD_verify
4638 * Display verify flag.
4639 * FIXME: We don't actually do anything with the verify flag other than toggle
4640 * it...
4643 void WCMD_verify (const WCHAR *args) {
4645 int count;
4647 count = lstrlenW(args);
4648 if (count == 0) {
4649 if (verify_mode) WCMD_output(WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"ON");
4650 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"OFF");
4651 return;
4653 if (lstrcmpiW(args, L"ON") == 0) {
4654 verify_mode = TRUE;
4655 return;
4657 else if (lstrcmpiW(args, L"OFF") == 0) {
4658 verify_mode = FALSE;
4659 return;
4661 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4664 /****************************************************************************
4665 * WCMD_version
4667 * Display version info.
4670 void WCMD_version (void) {
4672 WCMD_output_asis (version_string);
4676 /****************************************************************************
4677 * WCMD_volume
4679 * Display volume information (set_label = FALSE)
4680 * Additionally set volume label (set_label = TRUE)
4681 * Returns 1 on success, 0 otherwise
4684 int WCMD_volume(BOOL set_label, const WCHAR *path)
4686 DWORD count, serial;
4687 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4688 BOOL status;
4690 if (!*path) {
4691 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
4692 if (!status) {
4693 WCMD_print_error ();
4694 return 0;
4696 status = GetVolumeInformationW(NULL, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4698 else {
4699 if ((path[1] != ':') || (lstrlenW(path) != 2)) {
4700 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4701 return 0;
4703 wsprintfW (curdir, L"%s\\", path);
4704 status = GetVolumeInformationW(curdir, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4706 if (!status) {
4707 WCMD_print_error ();
4708 return 0;
4710 if (label[0] != '\0') {
4711 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4712 curdir[0], label);
4714 else {
4715 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4716 curdir[0]);
4718 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4719 HIWORD(serial), LOWORD(serial));
4720 if (set_label) {
4721 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4722 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
4723 if (count > 1) {
4724 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4725 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4727 if (*path) {
4728 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4730 else {
4731 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4734 return 1;
4737 /**************************************************************************
4738 * WCMD_exit
4740 * Exit either the process, or just this batch program
4744 void WCMD_exit (CMD_LIST **cmdList) {
4745 int rc = wcstol(param1, NULL, 10); /* Note: wcstol of empty parameter is 0 */
4747 if (context && lstrcmpiW(quals, L"/B") == 0) {
4748 errorlevel = rc;
4749 context -> skip_rest = TRUE;
4750 *cmdList = NULL;
4751 } else {
4752 ExitProcess(rc);
4757 /*****************************************************************************
4758 * WCMD_assoc
4760 * Lists or sets file associations (assoc = TRUE)
4761 * Lists or sets file types (assoc = FALSE)
4763 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4765 HKEY key;
4766 DWORD accessOptions = KEY_READ;
4767 WCHAR *newValue;
4768 LONG rc = ERROR_SUCCESS;
4769 WCHAR keyValue[MAXSTRING];
4770 DWORD valueLen = MAXSTRING;
4771 HKEY readKey;
4773 /* See if parameter includes '=' */
4774 errorlevel = 0;
4775 newValue = wcschr(args, '=');
4776 if (newValue) accessOptions |= KEY_WRITE;
4778 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4779 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, L"", 0, accessOptions, &key) != ERROR_SUCCESS) {
4780 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
4781 return;
4784 /* If no parameters then list all associations */
4785 if (*args == 0x00) {
4786 int index = 0;
4788 /* Enumerate all the keys */
4789 while (rc != ERROR_NO_MORE_ITEMS) {
4790 WCHAR keyName[MAXSTRING];
4791 DWORD nameLen;
4793 /* Find the next value */
4794 nameLen = MAXSTRING;
4795 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4797 if (rc == ERROR_SUCCESS) {
4799 /* Only interested in extension ones if assoc, or others
4800 if not assoc */
4801 if ((keyName[0] == '.' && assoc) ||
4802 (!(keyName[0] == '.') && (!assoc)))
4804 WCHAR subkey[MAXSTRING];
4805 lstrcpyW(subkey, keyName);
4806 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4808 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4810 valueLen = ARRAY_SIZE(keyValue);
4811 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4812 WCMD_output_asis(keyName);
4813 WCMD_output_asis(L"=");
4814 /* If no default value found, leave line empty after '=' */
4815 if (rc == ERROR_SUCCESS) {
4816 WCMD_output_asis(keyValue);
4818 WCMD_output_asis(L"\r\n");
4819 RegCloseKey(readKey);
4825 } else {
4827 /* Parameter supplied - if no '=' on command line, it's a query */
4828 if (newValue == NULL) {
4829 WCHAR *space;
4830 WCHAR subkey[MAXSTRING];
4832 /* Query terminates the parameter at the first space */
4833 lstrcpyW(keyValue, args);
4834 space = wcschr(keyValue, ' ');
4835 if (space) *space=0x00;
4837 /* Set up key name */
4838 lstrcpyW(subkey, keyValue);
4839 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4841 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4843 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4844 WCMD_output_asis(args);
4845 WCMD_output_asis(L"=");
4846 /* If no default value found, leave line empty after '=' */
4847 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4848 WCMD_output_asis(L"\r\n");
4849 RegCloseKey(readKey);
4851 } else {
4852 WCHAR msgbuffer[MAXSTRING];
4854 /* Load the translated 'File association not found' */
4855 if (assoc) {
4856 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4857 } else {
4858 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4860 WCMD_output_stderr(msgbuffer, keyValue);
4861 errorlevel = 2;
4864 /* Not a query - it's a set or clear of a value */
4865 } else {
4867 WCHAR subkey[MAXSTRING];
4869 /* Get pointer to new value */
4870 *newValue = 0x00;
4871 newValue++;
4873 /* Set up key name */
4874 lstrcpyW(subkey, args);
4875 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4877 /* If nothing after '=' then clear value - only valid for ASSOC */
4878 if (*newValue == 0x00) {
4880 if (assoc) rc = RegDeleteKeyW(key, args);
4881 if (assoc && rc == ERROR_SUCCESS) {
4882 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4884 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4885 WCMD_print_error();
4886 errorlevel = 2;
4888 } else {
4889 WCHAR msgbuffer[MAXSTRING];
4891 /* Load the translated 'File association not found' */
4892 if (assoc) {
4893 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4894 } else {
4895 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4897 WCMD_output_stderr(msgbuffer, args);
4898 errorlevel = 2;
4901 /* It really is a set value = contents */
4902 } else {
4903 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4904 accessOptions, NULL, &readKey, NULL);
4905 if (rc == ERROR_SUCCESS) {
4906 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4907 (LPBYTE)newValue,
4908 sizeof(WCHAR) * (lstrlenW(newValue) + 1));
4909 RegCloseKey(readKey);
4912 if (rc != ERROR_SUCCESS) {
4913 WCMD_print_error();
4914 errorlevel = 2;
4915 } else {
4916 WCMD_output_asis(args);
4917 WCMD_output_asis(L"=");
4918 WCMD_output_asis(newValue);
4919 WCMD_output_asis(L"\r\n");
4925 /* Clean up */
4926 RegCloseKey(key);
4929 /****************************************************************************
4930 * WCMD_color
4932 * Colors the terminal screen.
4935 void WCMD_color (void) {
4937 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4938 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4940 if (param1[0] != 0x00 && lstrlenW(param1) > 2) {
4941 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4942 return;
4945 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4947 COORD topLeft;
4948 DWORD screenSize;
4949 DWORD color = 0;
4951 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4953 topLeft.X = 0;
4954 topLeft.Y = 0;
4956 /* Convert the color hex digits */
4957 if (param1[0] == 0x00) {
4958 color = defaultColor;
4959 } else {
4960 color = wcstoul(param1, NULL, 16);
4963 /* Fail if fg == bg color */
4964 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4965 errorlevel = 1;
4966 return;
4969 /* Set the current screen contents and ensure all future writes
4970 remain this color */
4971 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
4972 SetConsoleTextAttribute(hStdOut, color);
4976 /****************************************************************************
4977 * WCMD_mklink
4980 void WCMD_mklink(WCHAR *args)
4982 int argno = 0;
4983 WCHAR *argN = args;
4984 BOOL isdir = FALSE;
4985 BOOL junction = FALSE;
4986 BOOL hard = FALSE;
4987 BOOL ret = FALSE;
4988 WCHAR file1[MAX_PATH];
4989 WCHAR file2[MAX_PATH];
4991 if (param1[0] == 0x00 || param2[0] == 0x00) {
4992 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4993 return;
4996 file1[0] = 0;
4998 while (argN) {
4999 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
5001 if (!argN) break;
5003 WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
5005 if (lstrcmpiW(thisArg, L"/D") == 0)
5006 isdir = TRUE;
5007 else if (lstrcmpiW(thisArg, L"/H") == 0)
5008 hard = TRUE;
5009 else if (lstrcmpiW(thisArg, L"/J") == 0)
5010 junction = TRUE;
5011 else {
5012 if(!file1[0])
5013 lstrcpyW(file1, thisArg);
5014 else
5015 lstrcpyW(file2, thisArg);
5019 if(hard)
5020 ret = CreateHardLinkW(file1, file2, NULL);
5021 else if(!junction)
5022 ret = CreateSymbolicLinkW(file1, file2, isdir);
5023 else
5024 WINE_TRACE("Juction links currently not supported.\n");
5026 if(!ret)
5027 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), file1);