mfplat/sample: Refactor sample_CopyToBuffer().
[wine.git] / programs / cmd / builtins.c
blobca703af52ecd172a03eb253ac32c492a6199fe28
1 /*
2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * FIXME:
24 * - No support for pipes, shell parameters
25 * - Lots of functionality missing from builtins
26 * - Messages etc need international support
29 #define WIN32_LEAN_AND_MEAN
31 #include "wcmd.h"
32 #include <shellapi.h>
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
37 extern int defaultColor;
38 extern BOOL echo_mode;
39 extern BOOL interactive;
41 struct env_stack *pushd_directories;
42 const WCHAR inbuilt[][10] = {
43 L"CALL",
44 L"CD",
45 L"CHDIR",
46 L"CLS",
47 L"COPY",
48 L"CTTY",
49 L"DATE",
50 L"DEL",
51 L"DIR",
52 L"ECHO",
53 L"ERASE",
54 L"FOR",
55 L"GOTO",
56 L"HELP",
57 L"IF",
58 L"LABEL",
59 L"MD",
60 L"MKDIR",
61 L"MOVE",
62 L"PATH",
63 L"PAUSE",
64 L"PROMPT",
65 L"REM",
66 L"REN",
67 L"RENAME",
68 L"RD",
69 L"RMDIR",
70 L"SET",
71 L"SHIFT",
72 L"START",
73 L"TIME",
74 L"TITLE",
75 L"TYPE",
76 L"VERIFY",
77 L"VER",
78 L"VOL",
79 L"ENDLOCAL",
80 L"SETLOCAL",
81 L"PUSHD",
82 L"POPD",
83 L"ASSOC",
84 L"COLOR",
85 L"FTYPE",
86 L"MORE",
87 L"CHOICE",
88 L"MKLINK",
89 L"EXIT"
91 static const WCHAR externals[][10] = {
92 L"ATTRIB",
93 L"XCOPY"
96 static HINSTANCE hinst;
97 struct env_stack *saved_environment;
98 static BOOL verify_mode = FALSE;
100 /* set /a routines work from single character operators, but some of the
101 operators are multiple character ones, especially the assignment ones.
102 Temporarily represent these using the values below on the operator stack */
103 #define OP_POSITIVE 'P'
104 #define OP_NEGATIVE 'N'
105 #define OP_ASSSIGNMUL 'a'
106 #define OP_ASSSIGNDIV 'b'
107 #define OP_ASSSIGNMOD 'c'
108 #define OP_ASSSIGNADD 'd'
109 #define OP_ASSSIGNSUB 'e'
110 #define OP_ASSSIGNAND 'f'
111 #define OP_ASSSIGNNOT 'g'
112 #define OP_ASSSIGNOR 'h'
113 #define OP_ASSSIGNSHL 'i'
114 #define OP_ASSSIGNSHR 'j'
116 /* This maintains a stack of operators, holding both the operator precedence
117 and the single character representation of the operator in question */
118 typedef struct _OPSTACK
120 int precedence;
121 WCHAR op;
122 struct _OPSTACK *next;
123 } OPSTACK;
125 /* This maintains a stack of values, where each value can either be a
126 numeric value, or a string representing an environment variable */
127 typedef struct _VARSTACK
129 BOOL isnum;
130 WCHAR *variable;
131 int value;
132 struct _VARSTACK *next;
133 } VARSTACK;
135 /* This maintains a mapping between the calculated operator and the
136 single character representation for the assignment operators. */
137 static struct
139 WCHAR op;
140 WCHAR calculatedop;
141 } calcassignments[] =
143 {'*', OP_ASSSIGNMUL},
144 {'/', OP_ASSSIGNDIV},
145 {'%', OP_ASSSIGNMOD},
146 {'+', OP_ASSSIGNADD},
147 {'-', OP_ASSSIGNSUB},
148 {'&', OP_ASSSIGNAND},
149 {'^', OP_ASSSIGNNOT},
150 {'|', OP_ASSSIGNOR},
151 {'<', OP_ASSSIGNSHL},
152 {'>', OP_ASSSIGNSHR},
153 {' ',' '}
156 /**************************************************************************
157 * WCMD_ask_confirm
159 * Issue a message and ask for confirmation, waiting on a valid answer.
161 * Returns True if Y (or A) answer is selected
162 * If optionAll contains a pointer, ALL is allowed, and if answered
163 * set to TRUE
166 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
167 BOOL *optionAll) {
169 UINT msgid;
170 WCHAR confirm[MAXSTRING];
171 WCHAR options[MAXSTRING];
172 WCHAR Ybuffer[MAXSTRING];
173 WCHAR Nbuffer[MAXSTRING];
174 WCHAR Abuffer[MAXSTRING];
175 WCHAR answer[MAX_PATH] = {'\0'};
176 DWORD count = 0;
178 /* Load the translated valid answers */
179 if (showSureText)
180 LoadStringW(hinst, WCMD_CONFIRM, confirm, ARRAY_SIZE(confirm));
181 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
182 LoadStringW(hinst, msgid, options, ARRAY_SIZE(options));
183 LoadStringW(hinst, WCMD_YES, Ybuffer, ARRAY_SIZE(Ybuffer));
184 LoadStringW(hinst, WCMD_NO, Nbuffer, ARRAY_SIZE(Nbuffer));
185 LoadStringW(hinst, WCMD_ALL, Abuffer, ARRAY_SIZE(Abuffer));
187 /* Loop waiting on a valid answer */
188 if (optionAll)
189 *optionAll = FALSE;
190 while (1)
192 WCMD_output_asis (message);
193 if (showSureText)
194 WCMD_output_asis (confirm);
195 WCMD_output_asis (options);
196 if (!WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, ARRAY_SIZE(answer), &count))
197 return FALSE;
198 answer[0] = towupper(answer[0]);
199 if (answer[0] == Ybuffer[0])
200 return TRUE;
201 if (answer[0] == Nbuffer[0])
202 return FALSE;
203 if (optionAll && answer[0] == Abuffer[0])
205 *optionAll = TRUE;
206 return TRUE;
211 /****************************************************************************
212 * WCMD_clear_screen
214 * Clear the terminal screen.
217 void WCMD_clear_screen (void) {
219 /* Emulate by filling the screen from the top left to bottom right with
220 spaces, then moving the cursor to the top left afterwards */
221 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
222 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
224 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
226 COORD topLeft;
227 DWORD screenSize, written;
229 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
231 topLeft.X = 0;
232 topLeft.Y = 0;
233 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
234 FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
235 SetConsoleCursorPosition(hStdOut, topLeft);
239 /****************************************************************************
240 * WCMD_change_tty
242 * Change the default i/o device (ie redirect STDin/STDout).
245 void WCMD_change_tty (void) {
247 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
251 /****************************************************************************
252 * WCMD_choice
256 void WCMD_choice (const WCHAR * args) {
257 WCHAR answer[16];
258 WCHAR buffer[16];
259 WCHAR *ptr = NULL;
260 WCHAR *opt_c = NULL;
261 WCHAR *my_command = NULL;
262 WCHAR opt_default = 0;
263 DWORD opt_timeout = 0;
264 DWORD count;
265 DWORD oldmode;
266 BOOL have_console;
267 BOOL opt_n = FALSE;
268 BOOL opt_s = FALSE;
270 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
271 errorlevel = 0;
273 my_command = xstrdupW(WCMD_skip_leading_spaces((WCHAR*)args));
275 ptr = WCMD_skip_leading_spaces(my_command);
276 while (*ptr == '/') {
277 switch (towupper(ptr[1])) {
278 case 'C':
279 ptr += 2;
280 /* the colon is optional */
281 if (*ptr == ':')
282 ptr++;
284 if (!*ptr || iswspace(*ptr)) {
285 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
286 free(my_command);
287 return;
290 /* remember the allowed keys (overwrite previous /C option) */
291 opt_c = ptr;
292 while (*ptr && (!iswspace(*ptr)))
293 ptr++;
295 if (*ptr) {
296 /* terminate allowed chars */
297 *ptr = 0;
298 ptr = WCMD_skip_leading_spaces(&ptr[1]);
300 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
301 break;
303 case 'N':
304 opt_n = TRUE;
305 ptr = WCMD_skip_leading_spaces(&ptr[2]);
306 break;
308 case 'S':
309 opt_s = TRUE;
310 ptr = WCMD_skip_leading_spaces(&ptr[2]);
311 break;
313 case 'T':
314 ptr = &ptr[2];
315 /* the colon is optional */
316 if (*ptr == ':')
317 ptr++;
319 opt_default = *ptr++;
321 if (!opt_default || (*ptr != ',')) {
322 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
323 free(my_command);
324 return;
326 ptr++;
328 count = 0;
329 while (((answer[count] = *ptr)) && iswdigit(*ptr) && (count < 15)) {
330 count++;
331 ptr++;
334 answer[count] = 0;
335 opt_timeout = wcstol(answer, NULL, 10);
337 ptr = WCMD_skip_leading_spaces(ptr);
338 break;
340 default:
341 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
342 free(my_command);
343 return;
347 if (opt_timeout)
348 WINE_FIXME("timeout not supported: %c,%ld\n", opt_default, opt_timeout);
350 if (have_console)
351 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
353 /* use default keys, when needed: localized versions of "Y"es and "No" */
354 if (!opt_c) {
355 LoadStringW(hinst, WCMD_YES, buffer, ARRAY_SIZE(buffer));
356 LoadStringW(hinst, WCMD_NO, buffer + 1, ARRAY_SIZE(buffer) - 1);
357 opt_c = buffer;
358 buffer[2] = 0;
361 /* print the question, when needed */
362 if (*ptr)
363 WCMD_output_asis(ptr);
365 if (!opt_s) {
366 wcsupr(opt_c);
367 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
370 if (!opt_n) {
371 /* print a list of all allowed answers inside brackets */
372 WCMD_output_asis(L"[");
373 ptr = opt_c;
374 answer[1] = 0;
375 while ((answer[0] = *ptr++)) {
376 WCMD_output_asis(answer);
377 if (*ptr)
378 WCMD_output_asis(L",");
380 WCMD_output_asis(L"]?");
383 while (TRUE) {
385 /* FIXME: Add support for option /T */
386 answer[1] = 0; /* terminate single character string */
387 if (!WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count))
389 free(my_command);
390 errorlevel = 0;
391 return;
394 if (!opt_s)
395 answer[0] = towupper(answer[0]);
397 ptr = wcschr(opt_c, answer[0]);
398 if (ptr) {
399 WCMD_output_asis(answer);
400 WCMD_output_asis(L"\r\n");
401 if (have_console)
402 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
404 errorlevel = (ptr - opt_c) + 1;
405 WINE_TRACE("answer: %ld\n", errorlevel);
406 free(my_command);
407 return;
409 else
411 /* key not allowed: play the bell */
412 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
413 WCMD_output_asis(L"\a");
418 /****************************************************************************
419 * WCMD_AppendEOF
421 * Adds an EOF onto the end of a file
422 * Returns TRUE on success
424 static BOOL WCMD_AppendEOF(WCHAR *filename)
426 HANDLE h;
427 DWORD bytes_written;
429 char eof = '\x1a';
431 WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
432 h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
433 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
435 if (h == INVALID_HANDLE_VALUE) {
436 WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(filename), GetLastError());
437 return FALSE;
438 } else {
439 SetFilePointer (h, 0, NULL, FILE_END);
440 if (!WriteFile(h, &eof, 1, &bytes_written, NULL)) {
441 WINE_ERR("Failed to append EOF to %s (%ld)\n", wine_dbgstr_w(filename), GetLastError());
442 CloseHandle(h);
443 return FALSE;
445 CloseHandle(h);
447 return TRUE;
450 /****************************************************************************
451 * WCMD_IsSameFile
453 * Checks if the two paths reference to the same file
455 static BOOL WCMD_IsSameFile(const WCHAR *name1, const WCHAR *name2)
457 BOOL ret = FALSE;
458 HANDLE file1 = INVALID_HANDLE_VALUE, file2 = INVALID_HANDLE_VALUE;
459 BY_HANDLE_FILE_INFORMATION info1, info2;
461 file1 = CreateFileW(name1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
462 if (file1 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file1, &info1))
463 goto end;
465 file2 = CreateFileW(name2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
466 if (file2 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file2, &info2))
467 goto end;
469 ret = info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
470 && info1.nFileIndexHigh == info2.nFileIndexHigh
471 && info1.nFileIndexLow == info2.nFileIndexLow;
472 end:
473 if (file1 != INVALID_HANDLE_VALUE)
474 CloseHandle(file1);
475 if (file2 != INVALID_HANDLE_VALUE)
476 CloseHandle(file2);
477 return ret;
480 /****************************************************************************
481 * WCMD_ManualCopy
483 * Copies from a file
484 * optionally reading only until EOF (ascii copy)
485 * optionally appending onto an existing file (append)
486 * Returns TRUE on success
488 static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
490 HANDLE in,out;
491 BOOL ok;
492 DWORD bytesread, byteswritten;
494 WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
495 wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
497 in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
498 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
499 if (in == INVALID_HANDLE_VALUE) {
500 WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(srcname), GetLastError());
501 return FALSE;
504 /* Open the output file, overwriting if not appending */
505 out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
506 append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
507 if (out == INVALID_HANDLE_VALUE) {
508 WINE_ERR("Failed to open %s (%ld)\n", wine_dbgstr_w(dstname), GetLastError());
509 CloseHandle(in);
510 return FALSE;
513 /* Move to end of destination if we are going to append to it */
514 if (append) {
515 SetFilePointer(out, 0, NULL, FILE_END);
518 /* Loop copying data from source to destination until EOF read */
521 char buffer[MAXSTRING];
523 ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
524 if (ok) {
526 /* Stop at first EOF */
527 if (ascii) {
528 char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
529 if (ptr) bytesread = (ptr - buffer);
532 if (bytesread) {
533 ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
534 if (!ok || byteswritten != bytesread) {
535 WINE_ERR("Unexpected failure writing to %s, rc=%ld\n",
536 wine_dbgstr_w(dstname), GetLastError());
539 } else {
540 WINE_ERR("Unexpected failure reading from %s, rc=%ld\n",
541 wine_dbgstr_w(srcname), GetLastError());
543 } while (ok && bytesread > 0);
545 CloseHandle(out);
546 CloseHandle(in);
547 return ok;
550 /****************************************************************************
551 * WCMD_copy
553 * Copy a file or wildcarded set.
554 * For ascii/binary type copies, it gets complex:
555 * Syntax on command line is
556 * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
557 * Where first /a or /b sets 'mode in operation' until another is found
558 * once another is found, it applies to the file preceding the /a or /b
559 * In addition each filename can contain wildcards
560 * To make matters worse, the + may be in the same parameter (i.e. no
561 * whitespace) or with whitespace separating it
563 * ASCII mode on read == read and stop at first EOF
564 * ASCII mode on write == append EOF to destination
565 * Binary == copy as-is
567 * Design of this is to build up a list of files which will be copied into a
568 * list, then work through the list file by file.
569 * If no destination is specified, it defaults to the name of the first file in
570 * the list, but the current directory.
574 void WCMD_copy(WCHAR * args) {
576 BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
577 WCHAR *thisparam;
578 int argno = 0;
579 WCHAR *rawarg;
580 WIN32_FIND_DATAW fd;
581 HANDLE hff = INVALID_HANDLE_VALUE;
582 int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
583 BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
584 BOOL anyconcats = FALSE; /* Have we found any + options */
585 BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
586 BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
587 BOOL prompt; /* Prompt before overwriting */
588 WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
589 BOOL destisdirectory = FALSE; /* Is the destination a directory? */
590 BOOL status;
591 WCHAR copycmd[4];
592 DWORD len;
593 BOOL dstisdevice = FALSE;
595 typedef struct _COPY_FILES
597 struct _COPY_FILES *next;
598 BOOL concatenate;
599 WCHAR *name;
600 int binarycopy;
601 } COPY_FILES;
602 COPY_FILES *sourcelist = NULL;
603 COPY_FILES *lastcopyentry = NULL;
604 COPY_FILES *destination = NULL;
605 COPY_FILES *thiscopy = NULL;
606 COPY_FILES *prevcopy = NULL;
608 /* Assume we were successful! */
609 errorlevel = 0;
611 /* If no args supplied at all, report an error */
612 if (param1[0] == 0x00) {
613 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
614 errorlevel = 1;
615 return;
618 opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
620 /* Walk through all args, building up a list of files to process */
621 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
622 while (*(thisparam)) {
623 WCHAR *pos1, *pos2;
624 BOOL inquotes;
626 WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
628 /* Handle switches */
629 if (*thisparam == '/') {
630 while (*thisparam == '/') {
631 thisparam++;
632 if (towupper(*thisparam) == 'D') {
633 opt_d = TRUE;
634 if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
635 } else if (towupper(*thisparam) == 'Y') {
636 opt_y = TRUE;
637 } else if (towupper(*thisparam) == '-' && towupper(*(thisparam+1)) == 'Y') {
638 opt_noty = TRUE;
639 } else if (towupper(*thisparam) == 'V') {
640 opt_v = TRUE;
641 if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
642 } else if (towupper(*thisparam) == 'N') {
643 opt_n = TRUE;
644 if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
645 } else if (towupper(*thisparam) == 'Z') {
646 opt_z = TRUE;
647 if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
648 } else if (towupper(*thisparam) == 'A') {
649 if (binarymode != 0) {
650 binarymode = 0;
651 WINE_TRACE("Subsequent files will be handled as ASCII\n");
652 if (destination != NULL) {
653 WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
654 destination->binarycopy = binarymode;
655 } else if (lastcopyentry != NULL) {
656 WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
657 lastcopyentry->binarycopy = binarymode;
660 } else if (towupper(*thisparam) == 'B') {
661 if (binarymode != 1) {
662 binarymode = 1;
663 WINE_TRACE("Subsequent files will be handled as binary\n");
664 if (destination != NULL) {
665 WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
666 destination->binarycopy = binarymode;
667 } else if (lastcopyentry != NULL) {
668 WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
669 lastcopyentry->binarycopy = binarymode;
672 } else {
673 WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
675 thisparam++;
678 /* This parameter was purely switches, get the next one */
679 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
680 continue;
683 /* We have found something which is not a switch. If could be anything of the form
684 sourcefilename (which could be destination too)
685 + (when filename + filename syntex used)
686 sourcefilename+sourcefilename
687 +sourcefilename
688 +/b[tests show windows then ignores to end of parameter]
691 if (*thisparam=='+') {
692 if (lastcopyentry == NULL) {
693 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
694 errorlevel = 1;
695 goto exitreturn;
696 } else {
697 concatnextfilename = TRUE;
698 anyconcats = TRUE;
701 /* Move to next thing to process */
702 thisparam++;
703 if (*thisparam == 0x00)
704 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
705 continue;
708 /* We have found something to process - build a COPY_FILE block to store it */
709 thiscopy = xalloc(sizeof(COPY_FILES));
711 WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
712 thiscopy->concatenate = concatnextfilename;
713 thiscopy->binarycopy = binarymode;
714 thiscopy->next = NULL;
716 /* Time to work out the name. Allocate at least enough space (deliberately too much to
717 leave space to append \* to the end) , then copy in character by character. Strip off
718 quotes if we find them. */
719 len = lstrlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
720 thiscopy->name = xalloc(len * sizeof(WCHAR));
721 memset(thiscopy->name, 0x00, len);
723 pos1 = thisparam;
724 pos2 = thiscopy->name;
725 inquotes = FALSE;
726 while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
727 if (*pos1 == '"') {
728 inquotes = !inquotes;
729 pos1++;
730 } else *pos2++ = *pos1++;
732 *pos2 = 0;
733 WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
735 /* This is either the first source, concatenated subsequent source or destination */
736 if (sourcelist == NULL) {
737 WINE_TRACE("Adding as first source part\n");
738 sourcelist = thiscopy;
739 lastcopyentry = thiscopy;
740 } else if (concatnextfilename) {
741 WINE_TRACE("Adding to source file list to be concatenated\n");
742 lastcopyentry->next = thiscopy;
743 lastcopyentry = thiscopy;
744 } else if (destination == NULL) {
745 destination = thiscopy;
746 } else {
747 /* We have processed sources and destinations and still found more to do - invalid */
748 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
749 errorlevel = 1;
750 goto exitreturn;
752 concatnextfilename = FALSE;
754 /* We either need to process the rest of the parameter or move to the next */
755 if (*pos1 == '/' || *pos1 == '+') {
756 thisparam = pos1;
757 continue;
758 } else {
759 thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
763 /* Ensure we have at least one source file */
764 if (!sourcelist) {
765 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
766 errorlevel = 1;
767 goto exitreturn;
770 /* Default whether automatic overwriting is on. If we are interactive then
771 we prompt by default, otherwise we overwrite by default
772 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
773 if (opt_noty) prompt = TRUE;
774 else if (opt_y) prompt = FALSE;
775 else {
776 /* By default, we will force the overwrite in batch mode and ask for
777 * confirmation in interactive mode. */
778 prompt = interactive;
779 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
780 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
781 * default behavior. */
782 len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
783 if (len && len < ARRAY_SIZE(copycmd)) {
784 if (!lstrcmpiW(copycmd, L"/Y"))
785 prompt = FALSE;
786 else if (!lstrcmpiW(copycmd, L"/-Y"))
787 prompt = TRUE;
791 /* Calculate the destination now - if none supplied, it's current dir +
792 filename of first file in list*/
793 if (destination == NULL) {
795 WINE_TRACE("No destination supplied, so need to calculate it\n");
796 lstrcpyW(destname, L".");
797 lstrcatW(destname, L"\\");
799 destination = xalloc(sizeof(COPY_FILES));
800 if (destination == NULL) goto exitreturn;
801 destination->concatenate = FALSE; /* Not used for destination */
802 destination->binarycopy = binarymode;
803 destination->next = NULL; /* Not used for destination */
804 destination->name = NULL; /* To be filled in */
805 destisdirectory = TRUE;
807 } else {
808 WCHAR *filenamepart;
809 DWORD attributes;
811 WINE_TRACE("Destination supplied, processing to see if file or directory\n");
813 /* Convert to fully qualified path/filename */
814 if (!WCMD_get_fullpath(destination->name, ARRAY_SIZE(destname), destname, &filenamepart)) return;
815 WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
817 /* If parameter is a directory, ensure it ends in \ */
818 attributes = GetFileAttributesW(destname);
819 if (ends_with_backslash( destname ) ||
820 ((attributes != INVALID_FILE_ATTRIBUTES) &&
821 (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
823 destisdirectory = TRUE;
824 if (!ends_with_backslash(destname)) lstrcatW(destname, L"\\");
825 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
829 /* Normally, the destination is the current directory unless we are
830 concatenating, in which case it's current directory plus first filename.
831 Note that if the
832 In addition by default it is a binary copy unless concatenating, when
833 the copy defaults to an ascii copy (stop at EOF). We do not know the
834 first source part yet (until we search) so flag as needing filling in. */
836 if (anyconcats) {
837 /* We have found an a+b type syntax, so destination has to be a filename
838 and we need to default to ascii copying. If we have been supplied a
839 directory as the destination, we need to defer calculating the name */
840 if (destisdirectory) appendfirstsource = TRUE;
841 if (destination->binarycopy == -1) destination->binarycopy = 0;
843 } else if (!destisdirectory) {
844 /* We have been asked to copy to a filename. Default to ascii IF the
845 source contains wildcards (true even if only one match) */
846 if (wcspbrk(sourcelist->name, L"*?") != NULL) {
847 anyconcats = TRUE; /* We really are concatenating to a single file */
848 if (destination->binarycopy == -1) {
849 destination->binarycopy = 0;
851 } else {
852 if (destination->binarycopy == -1) {
853 destination->binarycopy = 1;
858 /* Save away the destination name*/
859 free(destination->name);
860 destination->name = xstrdupW(destname);
861 WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
862 wine_dbgstr_w(destname), appendfirstsource);
864 /* Remember if the destination is a device */
865 if (wcsncmp(destination->name, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
866 WINE_TRACE("Destination is a device\n");
867 dstisdevice = TRUE;
870 /* Now we need to walk the set of sources, and process each name we come to.
871 If anyconcats is true, we are writing to one file, otherwise we are using
872 the source name each time.
873 If destination exists, prompt for overwrite the first time (if concatenating
874 we ask each time until yes is answered)
875 The first source file we come across must exist (when wildcards expanded)
876 and if concatenating with overwrite prompts, each source file must exist
877 until a yes is answered. */
879 thiscopy = sourcelist;
880 prevcopy = NULL;
882 while (thiscopy != NULL) {
884 WCHAR srcpath[MAX_PATH];
885 const WCHAR *srcname;
886 WCHAR *filenamepart;
887 DWORD attributes;
888 BOOL srcisdevice = FALSE;
890 /* If it was not explicit, we now know whether we are concatenating or not and
891 hence whether to copy as binary or ascii */
892 if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
894 /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
895 to where the filename portion begins (used for wildcard expansion). */
896 if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return;
897 WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
899 /* If parameter is a directory, ensure it ends in \* */
900 attributes = GetFileAttributesW(srcpath);
901 if (ends_with_backslash( srcpath )) {
903 /* We need to know where the filename part starts, so append * and
904 recalculate the full resulting path */
905 lstrcatW(thiscopy->name, L"*");
906 if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return;
907 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
909 } else if ((wcspbrk(srcpath, L"*?") == NULL) &&
910 (attributes != INVALID_FILE_ATTRIBUTES) &&
911 (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
913 /* We need to know where the filename part starts, so append \* and
914 recalculate the full resulting path */
915 lstrcatW(thiscopy->name, L"\\*");
916 if (!WCMD_get_fullpath(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart)) return;
917 WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
920 WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
921 wine_dbgstr_w(srcpath), anyconcats);
923 /* If the source is a device, just use it, otherwise search */
924 if (wcsncmp(srcpath, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
925 WINE_TRACE("Source is a device\n");
926 srcisdevice = TRUE;
927 srcname = &srcpath[4]; /* After the \\.\ prefix */
928 } else {
930 /* Loop through all source files */
931 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
932 hff = FindFirstFileW(srcpath, &fd);
933 if (hff != INVALID_HANDLE_VALUE) {
934 srcname = fd.cFileName;
938 if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
939 do {
940 WCHAR outname[MAX_PATH];
941 BOOL overwrite;
942 BOOL appendtofirstfile = FALSE;
944 /* Skip . and .., and directories */
945 if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
946 WINE_TRACE("Skipping directories\n");
947 } else {
949 /* Build final destination name */
950 lstrcpyW(outname, destination->name);
951 if (destisdirectory || appendfirstsource) lstrcatW(outname, srcname);
953 /* Build source name */
954 if (!srcisdevice) lstrcpyW(filenamepart, srcname);
956 /* Do we just overwrite (we do if we are writing to a device) */
957 overwrite = !prompt;
958 if (dstisdevice || (anyconcats && writtenoneconcat)) {
959 overwrite = TRUE;
962 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
963 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
964 WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
965 thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
967 if (!writtenoneconcat) {
968 appendtofirstfile = anyconcats && WCMD_IsSameFile(srcpath, outname);
971 /* Prompt before overwriting */
972 if (appendtofirstfile) {
973 overwrite = TRUE;
974 } else if (!overwrite) {
975 DWORD attributes = GetFileAttributesW(outname);
976 if (attributes != INVALID_FILE_ATTRIBUTES) {
977 WCHAR* question;
978 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
979 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
980 LocalFree(question);
982 else overwrite = TRUE;
985 /* If we needed to save away the first filename, do it */
986 if (appendfirstsource && overwrite) {
987 free(destination->name);
988 destination->name = xstrdupW(outname);
989 WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
990 appendfirstsource = FALSE;
991 destisdirectory = FALSE;
994 /* Do the copy as appropriate */
995 if (overwrite) {
996 if (anyconcats && WCMD_IsSameFile(srcpath, outname)) {
997 /* Silently skip if the destination file is also a source file */
998 status = TRUE;
999 } else if (anyconcats && writtenoneconcat) {
1000 if (thiscopy->binarycopy) {
1001 status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
1002 } else {
1003 status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
1005 } else if (!thiscopy->binarycopy) {
1006 status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
1007 } else if (srcisdevice) {
1008 status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
1009 } else {
1010 status = CopyFileW(srcpath, outname, FALSE);
1012 if (!status) {
1013 WCMD_print_error ();
1014 errorlevel = 1;
1015 } else {
1016 WINE_TRACE("Copied successfully\n");
1017 if (anyconcats) writtenoneconcat = TRUE;
1019 /* Append EOF if ascii destination and we are not going to add more onto the end
1020 Note: Testing shows windows has an optimization whereas if you have a binary
1021 copy of a file to a single destination (ie concatenation) then it does not add
1022 the EOF, hence the check on the source copy type below. */
1023 if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
1024 if (!WCMD_AppendEOF(outname)) {
1025 WCMD_print_error ();
1026 errorlevel = 1;
1032 } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
1033 if (!srcisdevice) FindClose (hff);
1034 } else {
1035 /* Error if the first file was not found */
1036 if (!anyconcats || !writtenoneconcat) {
1037 WCMD_print_error ();
1038 errorlevel = 1;
1042 /* Step on to the next supplied source */
1043 thiscopy = thiscopy -> next;
1046 /* Append EOF if ascii destination and we were concatenating */
1047 if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
1048 if (!WCMD_AppendEOF(destination->name)) {
1049 WCMD_print_error ();
1050 errorlevel = 1;
1054 /* Exit out of the routine, freeing any remaining allocated memory */
1055 exitreturn:
1057 thiscopy = sourcelist;
1058 while (thiscopy != NULL) {
1059 prevcopy = thiscopy;
1060 /* Free up this block*/
1061 thiscopy = thiscopy -> next;
1062 free(prevcopy->name);
1063 free(prevcopy);
1066 /* Free up the destination memory */
1067 if (destination) {
1068 free(destination->name);
1069 free(destination);
1072 return;
1075 /****************************************************************************
1076 * WCMD_create_dir
1078 * Create a directory (and, if needed, any intermediate directories).
1080 * Modifies its argument by replacing slashes temporarily with nulls.
1083 static BOOL create_full_path(WCHAR* path)
1085 WCHAR *p, *start;
1087 /* don't mess with drive letter portion of path, if any */
1088 start = path;
1089 if (path[1] == ':')
1090 start = path+2;
1092 /* Strip trailing slashes. */
1093 for (p = path + lstrlenW(path) - 1; p != start && *p == '\\'; p--)
1094 *p = 0;
1096 /* Step through path, creating intermediate directories as needed. */
1097 /* First component includes drive letter, if any. */
1098 p = start;
1099 for (;;) {
1100 DWORD rv;
1101 /* Skip to end of component */
1102 while (*p == '\\') p++;
1103 while (*p && *p != '\\') p++;
1104 if (!*p) {
1105 /* path is now the original full path */
1106 return CreateDirectoryW(path, NULL);
1108 /* Truncate path, create intermediate directory, and restore path */
1109 *p = 0;
1110 rv = CreateDirectoryW(path, NULL);
1111 *p = '\\';
1112 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
1113 return FALSE;
1115 /* notreached */
1116 return FALSE;
1119 void WCMD_create_dir (WCHAR *args) {
1120 int argno = 0;
1121 WCHAR *argN = args;
1123 if (param1[0] == 0x00) {
1124 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1125 return;
1127 /* Loop through all args */
1128 while (TRUE) {
1129 WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
1130 if (!argN) break;
1131 if (!create_full_path(thisArg)) {
1132 WCMD_print_error ();
1133 errorlevel = 1;
1138 /* Parse the /A options given by the user on the commandline
1139 * into a bitmask of wanted attributes (*wantSet),
1140 * and a bitmask of unwanted attributes (*wantClear).
1142 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
1143 WCHAR *p;
1145 /* both are strictly 'out' parameters */
1146 *wantSet=0;
1147 *wantClear=0;
1149 /* For each /A argument */
1150 for (p=wcsstr(quals, L"/A"); p != NULL; p=wcsstr(p, L"/A")) {
1151 /* Skip /A itself */
1152 p += 2;
1154 /* Skip optional : */
1155 if (*p == ':') p++;
1157 /* For each of the attribute specifier chars to this /A option */
1158 for (; *p != 0 && *p != '/'; p++) {
1159 BOOL negate = FALSE;
1160 DWORD mask = 0;
1162 if (*p == '-') {
1163 negate=TRUE;
1164 p++;
1167 /* Convert the attribute specifier to a bit in one of the masks */
1168 switch (*p) {
1169 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
1170 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
1171 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
1172 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
1173 default:
1174 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1176 if (negate)
1177 *wantClear |= mask;
1178 else
1179 *wantSet |= mask;
1184 /* If filename part of parameter is * or *.*,
1185 * and neither /Q nor /P options were given,
1186 * prompt the user whether to proceed.
1187 * Returns FALSE if user says no, TRUE otherwise.
1188 * *pPrompted is set to TRUE if the user is prompted.
1189 * (If /P supplied, del will prompt for individual files later.)
1191 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
1192 if ((wcsstr(quals, L"/Q") == NULL) && (wcsstr(quals, L"/P") == NULL)) {
1193 WCHAR drive[10];
1194 WCHAR dir[MAX_PATH];
1195 WCHAR fname[MAX_PATH];
1196 WCHAR ext[MAX_PATH];
1197 WCHAR fpath[MAX_PATH];
1199 /* Convert path into actual directory spec */
1200 if (!WCMD_get_fullpath(filename, ARRAY_SIZE(fpath), fpath, NULL)) return FALSE;
1201 _wsplitpath(fpath, drive, dir, fname, ext);
1203 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
1204 if ((lstrcmpW(fname, L"*") == 0) && (*ext == 0x00 || (lstrcmpW(ext, L".*") == 0))) {
1206 WCHAR question[MAXSTRING];
1208 /* Caller uses this to suppress "file not found" warning later */
1209 *pPrompted = TRUE;
1211 /* Ask for confirmation */
1212 wsprintfW(question, L"%s ", fpath);
1213 return WCMD_ask_confirm(question, TRUE, NULL);
1216 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
1217 return TRUE;
1220 /* Helper function for WCMD_delete().
1221 * Deletes a single file, directory, or wildcard.
1222 * If /S was given, does it recursively.
1223 * Returns TRUE if a file was deleted.
1225 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
1226 DWORD wanted_attrs;
1227 DWORD unwanted_attrs;
1228 BOOL found = FALSE;
1229 WCHAR argCopy[MAX_PATH];
1230 WIN32_FIND_DATAW fd;
1231 HANDLE hff;
1232 WCHAR fpath[MAX_PATH];
1233 WCHAR *p;
1234 BOOL handleParm = TRUE;
1236 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
1238 lstrcpyW(argCopy, thisArg);
1239 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
1240 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
1242 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
1243 /* Skip this arg if user declines to delete *.* */
1244 return FALSE;
1247 /* First, try to delete in the current directory */
1248 hff = FindFirstFileW(argCopy, &fd);
1249 if (hff == INVALID_HANDLE_VALUE) {
1250 handleParm = FALSE;
1251 } else {
1252 found = TRUE;
1255 /* Support del <dirname> by just deleting all files dirname\* */
1256 if (handleParm
1257 && (wcschr(argCopy,'*') == NULL)
1258 && (wcschr(argCopy,'?') == NULL)
1259 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1261 WCHAR modifiedParm[MAX_PATH];
1263 lstrcpyW(modifiedParm, argCopy);
1264 lstrcatW(modifiedParm, L"\\*");
1265 FindClose(hff);
1266 found = TRUE;
1267 WCMD_delete_one(modifiedParm);
1269 } else if (handleParm) {
1271 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
1272 lstrcpyW (fpath, argCopy);
1273 do {
1274 p = wcsrchr (fpath, '\\');
1275 if (p != NULL) {
1276 *++p = '\0';
1277 lstrcatW (fpath, fd.cFileName);
1279 else lstrcpyW (fpath, fd.cFileName);
1280 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1281 BOOL ok;
1283 /* Handle attribute matching (/A) */
1284 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
1285 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
1287 /* /P means prompt for each file */
1288 if (ok && wcsstr(quals, L"/P") != NULL) {
1289 WCHAR* question;
1291 /* Ask for confirmation */
1292 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
1293 ok = WCMD_ask_confirm(question, FALSE, NULL);
1294 LocalFree(question);
1297 /* Only proceed if ok to */
1298 if (ok) {
1300 /* If file is read only, and /A:r or /F supplied, delete it */
1301 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
1302 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
1303 wcsstr(quals, L"/F") != NULL)) {
1304 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
1307 /* Now do the delete */
1308 if (!DeleteFileW(fpath)) WCMD_print_error ();
1312 } while (FindNextFileW(hff, &fd) != 0);
1313 FindClose (hff);
1316 /* Now recurse into all subdirectories handling the parameter in the same way */
1317 if (wcsstr(quals, L"/S") != NULL) {
1319 WCHAR thisDir[MAX_PATH];
1320 int cPos;
1322 WCHAR drive[10];
1323 WCHAR dir[MAX_PATH];
1324 WCHAR fname[MAX_PATH];
1325 WCHAR ext[MAX_PATH];
1327 /* Convert path into actual directory spec */
1328 if (!WCMD_get_fullpath(argCopy, ARRAY_SIZE(thisDir), thisDir, NULL)) return FALSE;
1330 _wsplitpath(thisDir, drive, dir, fname, ext);
1332 lstrcpyW(thisDir, drive);
1333 lstrcatW(thisDir, dir);
1334 cPos = lstrlenW(thisDir);
1336 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
1338 /* Append '*' to the directory */
1339 thisDir[cPos] = '*';
1340 thisDir[cPos+1] = 0x00;
1342 hff = FindFirstFileW(thisDir, &fd);
1344 /* Remove residual '*' */
1345 thisDir[cPos] = 0x00;
1347 if (hff != INVALID_HANDLE_VALUE) {
1348 DIRECTORY_STACK *allDirs = NULL;
1349 DIRECTORY_STACK *lastEntry = NULL;
1351 do {
1352 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1353 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0)) {
1355 DIRECTORY_STACK *nextDir;
1356 WCHAR subParm[MAX_PATH];
1358 if (wcslen(thisDir) + wcslen(fd.cFileName) + 1 + wcslen(fname) + wcslen(ext) >= MAX_PATH)
1360 WINE_TRACE("Skipping path too long %s%s\\%s%s\n",
1361 debugstr_w(thisDir), debugstr_w(fd.cFileName),
1362 debugstr_w(fname), debugstr_w(ext));
1363 continue;
1365 /* Work out search parameter in sub dir */
1366 lstrcpyW (subParm, thisDir);
1367 lstrcatW (subParm, fd.cFileName);
1368 lstrcatW (subParm, L"\\");
1369 lstrcatW (subParm, fname);
1370 lstrcatW (subParm, ext);
1371 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
1373 /* Allocate memory, add to list */
1374 nextDir = xalloc(sizeof(DIRECTORY_STACK));
1375 if (allDirs == NULL) allDirs = nextDir;
1376 if (lastEntry != NULL) lastEntry->next = nextDir;
1377 lastEntry = nextDir;
1378 nextDir->next = NULL;
1379 nextDir->dirName = xstrdupW(subParm);
1381 } while (FindNextFileW(hff, &fd) != 0);
1382 FindClose (hff);
1384 /* Go through each subdir doing the delete */
1385 while (allDirs != NULL) {
1386 DIRECTORY_STACK *tempDir;
1388 tempDir = allDirs->next;
1389 found |= WCMD_delete_one (allDirs->dirName);
1391 free(allDirs->dirName);
1392 free(allDirs);
1393 allDirs = tempDir;
1398 return found;
1401 /****************************************************************************
1402 * WCMD_delete
1404 * Delete a file or wildcarded set.
1406 * Note on /A:
1407 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
1408 * - Each set is a pattern, eg /ahr /as-r means
1409 * readonly+hidden OR nonreadonly system files
1410 * - The '-' applies to a single field, ie /a:-hr means read only
1411 * non-hidden files
1414 BOOL WCMD_delete (WCHAR *args) {
1415 int argno;
1416 WCHAR *argN;
1417 BOOL argsProcessed = FALSE;
1418 BOOL foundAny = FALSE;
1420 errorlevel = 0;
1422 for (argno=0; ; argno++) {
1423 BOOL found;
1424 WCHAR *thisArg;
1426 argN = NULL;
1427 thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
1428 if (!argN)
1429 break; /* no more parameters */
1430 if (argN[0] == '/')
1431 continue; /* skip options */
1433 argsProcessed = TRUE;
1434 found = WCMD_delete_one(thisArg);
1435 if (!found)
1436 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
1437 foundAny |= found;
1440 /* Handle no valid args */
1441 if (!argsProcessed)
1442 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1444 return foundAny;
1448 * WCMD_strtrim
1450 * Returns a trimmed version of s with all leading and trailing whitespace removed
1451 * Pre: s non NULL
1454 static WCHAR *WCMD_strtrim(const WCHAR *s)
1456 DWORD len = lstrlenW(s);
1457 const WCHAR *start = s;
1458 WCHAR* result;
1460 result = xalloc((len + 1) * sizeof(WCHAR));
1462 while (iswspace(*start)) start++;
1463 if (*start) {
1464 const WCHAR *end = s + len - 1;
1465 while (end > start && iswspace(*end)) end--;
1466 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
1467 result[end - start + 1] = '\0';
1468 } else {
1469 result[0] = '\0';
1472 return result;
1475 /****************************************************************************
1476 * WCMD_echo
1478 * Echo input to the screen (or not). We don't try to emulate the bugs
1479 * in DOS (try typing "ECHO ON AGAIN" for an example).
1482 void WCMD_echo (const WCHAR *args)
1484 int count;
1485 const WCHAR *origcommand = args;
1486 WCHAR *trimmed;
1488 if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
1489 || args[0]==':' || args[0]==';' || args[0]=='/')
1490 args++;
1492 trimmed = WCMD_strtrim(args);
1493 if (!trimmed) return;
1495 count = lstrlenW(trimmed);
1496 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
1497 && origcommand[0]!=';' && origcommand[0]!='/') {
1498 if (echo_mode) WCMD_output(WCMD_LoadMessage(WCMD_ECHOPROMPT), L"ON");
1499 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), L"OFF");
1500 free(trimmed);
1501 return;
1504 if (lstrcmpiW(trimmed, L"ON") == 0)
1505 echo_mode = TRUE;
1506 else if (lstrcmpiW(trimmed, L"OFF") == 0)
1507 echo_mode = FALSE;
1508 else {
1509 WCMD_output_asis (args);
1510 WCMD_output_asis(L"\r\n");
1512 free(trimmed);
1515 /*****************************************************************************
1516 * WCMD_part_execute
1518 * Execute a command, and any && or bracketed follow on to the command. The
1519 * first command to be executed may not be at the front of the
1520 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1522 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1523 BOOL isIF, BOOL executecmds)
1525 CMD_LIST *curPosition = *cmdList;
1526 int myDepth = (*cmdList)->bracketDepth;
1528 WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d), isIF(%d)\n", cmdList,
1529 wine_dbgstr_w(firstcmd), executecmds, isIF);
1531 /* Skip leading whitespace between condition and the command */
1532 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1534 /* Process the first command, if there is one */
1535 if (executecmds && firstcmd && *firstcmd) {
1536 WCHAR *command = xstrdupW(firstcmd);
1537 WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
1538 free(command);
1542 /* If it didn't move the position, step to next command */
1543 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1545 /* Process any other parts of the command */
1546 if (*cmdList) {
1547 BOOL processThese = executecmds;
1549 while (*cmdList) {
1550 /* execute all appropriate commands */
1551 curPosition = *cmdList;
1553 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d) processThese(%d)\n",
1554 *cmdList,
1555 (*cmdList)->prevDelim,
1556 (*cmdList)->bracketDepth,
1557 myDepth,
1558 processThese);
1560 /* Execute any statements appended to the line */
1561 /* FIXME: Only if previous call worked for && or failed for || */
1562 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1563 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1564 if (processThese && (*cmdList)->command) {
1565 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
1566 cmdList, FALSE);
1568 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1570 /* Execute any appended to the statement with (...) */
1571 } else if ((*cmdList)->bracketDepth > myDepth) {
1572 if (processThese) {
1573 *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
1574 } else {
1575 WINE_TRACE("Skipping command %p due to stack depth\n", *cmdList);
1577 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1579 /* End of the command - does 'ELSE ' follow as the next command? */
1580 } else {
1581 if (isIF && WCMD_keyword_ws_found(L"else", (*cmdList)->command)) {
1582 /* Swap between if and else processing */
1583 processThese = !executecmds;
1585 /* Process the ELSE part */
1586 if (processThese) {
1587 const int keyw_len = lstrlenW(L"else") + 1;
1588 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1590 /* Skip leading whitespace between condition and the command */
1591 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1592 if (*cmd) {
1593 WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
1595 } else {
1596 /* Loop skipping all commands until we get back to the current
1597 depth, including skipping commands and their subsequent
1598 pipes (eg cmd | prog) */
1599 do {
1600 *cmdList = (*cmdList)->nextcommand;
1601 } while (*cmdList &&
1602 ((*cmdList)->bracketDepth > myDepth ||
1603 (*cmdList)->prevDelim));
1605 /* After the else is complete, we need to now process subsequent commands */
1606 processThese = TRUE;
1608 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1610 /* If we were in an IF statement and we didn't find an else and yet we get back to
1611 the same bracket depth as the IF, then the IF statement is over. This is required
1612 to handle nested ifs properly */
1613 } else if (isIF && (*cmdList)->bracketDepth == myDepth) {
1614 if (WCMD_keyword_ws_found(L"do", (*cmdList)->command)) {
1615 WINE_TRACE("Still inside FOR-loop, not an end of IF statement\n");
1616 *cmdList = (*cmdList)->nextcommand;
1617 } else {
1618 WINE_TRACE("Found end of this nested IF statement, ending this if\n");
1619 break;
1621 } else if (!processThese) {
1622 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1623 WINE_TRACE("Skipping this command, as in not process mode (next = %p)\n", *cmdList);
1624 } else {
1625 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1626 break;
1631 return;
1634 static BOOL option_equals(WCHAR **haystack, const WCHAR *needle)
1636 int len = wcslen(needle);
1638 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1639 *haystack, len, needle, len) == CSTR_EQUAL) {
1640 *haystack += len;
1641 return TRUE;
1644 return FALSE;
1647 /*****************************************************************************
1648 * WCMD_parse_forf_options
1650 * Parses the for /f 'options', extracting the values and validating the
1651 * keywords. Note all keywords are optional.
1652 * Parameters:
1653 * options [I] The unparsed parameter string
1654 * eol [O] Set to the comment character (eol=x)
1655 * skip [O] Set to the number of lines to skip (skip=xx)
1656 * delims [O] Set to the token delimiters (delims=)
1657 * tokens [O] Set to the requested tokens, as provided (tokens=)
1658 * usebackq [O] Set to TRUE if usebackq found
1660 * Returns TRUE on success, FALSE on syntax error
1663 static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
1664 WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
1667 WCHAR *pos = options;
1668 int len = lstrlenW(pos);
1670 /* Initialize to defaults */
1671 lstrcpyW(delims, L" \t");
1672 lstrcpyW(tokens, L"1");
1673 *eol = 0;
1674 *skip = 0;
1675 *usebackq = FALSE;
1677 /* Strip (optional) leading and trailing quotes */
1678 if ((*pos == '"') && (pos[len-1] == '"')) {
1679 pos[len-1] = 0;
1680 pos++;
1683 /* Process each keyword */
1684 while (pos && *pos) {
1685 if (*pos == ' ' || *pos == '\t') {
1686 pos++;
1688 /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
1689 } else if (option_equals(&pos, L"eol=")) {
1690 *eol = *pos++;
1691 WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
1693 /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
1694 } else if (option_equals(&pos, L"skip=")) {
1695 WCHAR *nextchar = NULL;
1696 *skip = wcstoul(pos, &nextchar, 0);
1697 WINE_TRACE("Found skip as %d lines\n", *skip);
1698 pos = nextchar;
1700 /* Save if usebackq semantics are in effect */
1701 } else if (option_equals(&pos, L"usebackq")) {
1702 *usebackq = TRUE;
1703 WINE_TRACE("Found usebackq\n");
1705 /* Save the supplied delims. Slightly odd as space can be a delimiter but only
1706 if you finish the optionsroot string with delims= otherwise the space is
1707 just a token delimiter! */
1708 } else if (option_equals(&pos, L"delims=")) {
1709 int i=0;
1711 while (*pos && *pos != ' ') {
1712 delims[i++] = *pos;
1713 pos++;
1715 if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
1716 delims[i++] = 0; /* Null terminate the delims */
1717 WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
1719 /* Save the tokens being requested */
1720 } else if (option_equals(&pos, L"tokens=")) {
1721 int i=0;
1723 while (*pos && *pos != ' ') {
1724 tokens[i++] = *pos;
1725 pos++;
1727 tokens[i++] = 0; /* Null terminate the tokens */
1728 WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
1730 } else {
1731 WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
1732 return FALSE;
1735 return TRUE;
1738 /*****************************************************************************
1739 * WCMD_add_dirstowalk
1741 * When recursing through directories (for /r), we need to add to the list of
1742 * directories still to walk, any subdirectories of the one we are processing.
1744 * Parameters
1745 * options [I] The remaining list of directories still to process
1747 * Note this routine inserts the subdirectories found between the entry being
1748 * processed, and any other directory still to be processed, mimicking what
1749 * Windows does
1751 static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
1752 DIRECTORY_STACK *remainingDirs = dirsToWalk;
1753 WCHAR fullitem[MAX_PATH];
1754 WIN32_FIND_DATAW fd;
1755 HANDLE hff;
1757 /* Build a generic search and add all directories on the list of directories
1758 still to walk */
1759 lstrcpyW(fullitem, dirsToWalk->dirName);
1760 lstrcatW(fullitem, L"\\*");
1761 hff = FindFirstFileW(fullitem, &fd);
1762 if (hff != INVALID_HANDLE_VALUE) {
1763 do {
1764 WINE_TRACE("Looking for subdirectories\n");
1765 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
1766 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
1768 /* Allocate memory, add to list */
1769 DIRECTORY_STACK *toWalk;
1770 if (wcslen(dirsToWalk->dirName) + 1 + wcslen(fd.cFileName) >= MAX_PATH)
1772 WINE_TRACE("Skipping too long path %s\\%s\n",
1773 debugstr_w(dirsToWalk->dirName), debugstr_w(fd.cFileName));
1774 continue;
1776 toWalk = xalloc(sizeof(DIRECTORY_STACK));
1777 WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
1778 toWalk->next = remainingDirs->next;
1779 remainingDirs->next = toWalk;
1780 remainingDirs = toWalk;
1781 toWalk->dirName = xalloc(sizeof(WCHAR) * (wcslen(dirsToWalk->dirName) + 2 + wcslen(fd.cFileName)));
1782 lstrcpyW(toWalk->dirName, dirsToWalk->dirName);
1783 lstrcatW(toWalk->dirName, L"\\");
1784 lstrcatW(toWalk->dirName, fd.cFileName);
1785 WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
1786 toWalk, toWalk->next);
1788 } while (FindNextFileW(hff, &fd) != 0);
1789 WINE_TRACE("Finished adding all subdirectories\n");
1790 FindClose (hff);
1794 /**************************************************************************
1795 * WCMD_for_nexttoken
1797 * Parse the token= line, identifying the next highest number not processed
1798 * so far. Count how many tokens are referred (including duplicates) and
1799 * optionally return that, plus optionally indicate if the tokens= line
1800 * ends in a star.
1802 * Parameters:
1803 * lasttoken [I] - Identifies the token index of the last one
1804 * returned so far (-1 used for first loop)
1805 * tokenstr [I] - The specified tokens= line
1806 * firstCmd [O] - Optionally indicate how many tokens are listed
1807 * doAll [O] - Optionally indicate if line ends with *
1808 * duplicates [O] - Optionally indicate if there is any evidence of
1809 * overlaying tokens in the string
1810 * Note the caller should keep a running track of duplicates as the tokens
1811 * are recursively passed. If any have duplicates, then the * token should
1812 * not be honoured.
1814 static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
1815 int *totalfound, BOOL *doall,
1816 BOOL *duplicates)
1818 WCHAR *pos = tokenstr;
1819 int nexttoken = -1;
1821 if (totalfound) *totalfound = 0;
1822 if (doall) *doall = FALSE;
1823 if (duplicates) *duplicates = FALSE;
1825 WINE_TRACE("Find next token after %d in %s\n", lasttoken,
1826 wine_dbgstr_w(tokenstr));
1828 /* Loop through the token string, parsing it. Valid syntax is:
1829 token=m or x-y with comma delimiter and optionally * to finish*/
1830 while (*pos) {
1831 int nextnumber1, nextnumber2 = -1;
1832 WCHAR *nextchar;
1834 /* Remember if the next character is a star, it indicates a need to
1835 show all remaining tokens and should be the last character */
1836 if (*pos == '*') {
1837 if (doall) *doall = TRUE;
1838 if (totalfound) (*totalfound)++;
1839 /* If we have not found a next token to return, then indicate
1840 time to process the star */
1841 if (nexttoken == -1) {
1842 if (lasttoken == -1) {
1843 /* Special case the syntax of tokens=* which just means get whole line */
1844 nexttoken = 0;
1845 } else {
1846 nexttoken = lasttoken;
1849 break;
1852 /* Get the next number */
1853 nextnumber1 = wcstoul(pos, &nextchar, 10);
1855 /* If it is followed by a minus, it's a range, so get the next one as well */
1856 if (*nextchar == '-') {
1857 nextnumber2 = wcstoul(nextchar+1, &nextchar, 10);
1859 /* We want to return the lowest number that is higher than lasttoken
1860 but only if range is positive */
1861 if (nextnumber2 >= nextnumber1 &&
1862 lasttoken < nextnumber2) {
1864 int nextvalue;
1865 if (nexttoken == -1) {
1866 nextvalue = max(nextnumber1, (lasttoken+1));
1867 } else {
1868 nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
1871 /* Flag if duplicates identified */
1872 if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
1874 nexttoken = nextvalue;
1877 /* Update the running total for the whole range */
1878 if (nextnumber2 >= nextnumber1 && totalfound) {
1879 *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
1881 pos = nextchar;
1883 } else if (pos != nextchar) {
1884 if (totalfound) (*totalfound)++;
1886 /* See if the number found is one we have already seen */
1887 if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
1889 /* We want to return the lowest number that is higher than lasttoken */
1890 if (lasttoken < nextnumber1 &&
1891 ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
1892 nexttoken = nextnumber1;
1894 pos = nextchar;
1896 } else {
1897 /* Step on to the next character, usually over comma */
1898 if (*pos) pos++;
1903 /* Return result */
1904 if (nexttoken == -1) {
1905 WINE_TRACE("No next token found, previous was %d\n", lasttoken);
1906 nexttoken = lasttoken;
1907 } else if (nexttoken==lasttoken && doall && *doall) {
1908 WINE_TRACE("Request for all remaining tokens now\n");
1909 } else {
1910 WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
1912 if (totalfound) WINE_TRACE("Found total tokens to be %d\n", *totalfound);
1913 if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
1914 return nexttoken;
1917 /**************************************************************************
1918 * WCMD_parse_line
1920 * When parsing file or string contents (for /f), once the string to parse
1921 * has been identified, handle the various options and call the do part
1922 * if appropriate.
1924 * Parameters:
1925 * cmdStart [I] - Identifies the list of commands making up the
1926 * for loop body (especially if brackets in use)
1927 * firstCmd [I] - The textual start of the command after the DO
1928 * which is within the first item of cmdStart
1929 * cmdEnd [O] - Identifies where to continue after the DO
1930 * variable [I] - The variable identified on the for line
1931 * buffer [I] - The string to parse
1932 * doExecuted [O] - Set to TRUE if the DO is ever executed once
1933 * forf_skip [I/O] - How many lines to skip first
1934 * forf_eol [I] - The 'end of line' (comment) character
1935 * forf_delims [I] - The delimiters to use when breaking the string apart
1936 * forf_tokens [I] - The tokens to use when breaking the string apart
1938 static void WCMD_parse_line(CMD_LIST *cmdStart,
1939 const WCHAR *firstCmd,
1940 CMD_LIST **cmdEnd,
1941 const WCHAR variable,
1942 WCHAR *buffer,
1943 BOOL *doExecuted,
1944 int *forf_skip,
1945 WCHAR forf_eol,
1946 WCHAR *forf_delims,
1947 WCHAR *forf_tokens) {
1949 WCHAR *parm;
1950 FOR_CONTEXT oldcontext;
1951 int varidx, varoffset;
1952 int nexttoken, lasttoken = -1;
1953 BOOL starfound = FALSE;
1954 BOOL thisduplicate = FALSE;
1955 BOOL anyduplicates = FALSE;
1956 int totalfound;
1957 static WCHAR emptyW[] = L"";
1959 /* Skip lines if requested */
1960 if (*forf_skip) {
1961 (*forf_skip)--;
1962 return;
1965 /* Save away any existing for variable context (e.g. nested for loops) */
1966 oldcontext = forloopcontext;
1968 /* Extract the parameters based on the tokens= value (There will always
1969 be some value, as if it is not supplied, it defaults to tokens=1).
1970 Rough logic:
1971 Count how many tokens are named in the line, identify the lowest
1972 Empty (set to null terminated string) that number of named variables
1973 While lasttoken != nextlowest
1974 %letter = parameter number 'nextlowest'
1975 letter++ (if >26 or >52 abort)
1976 Go through token= string finding next lowest number
1977 If token ends in * set %letter = raw position of token(nextnumber+1)
1979 lasttoken = -1;
1980 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
1981 &starfound, &thisduplicate);
1982 varidx = FOR_VAR_IDX(variable);
1984 /* Empty out variables */
1985 for (varoffset=0;
1986 varidx >= 0 && varoffset<totalfound && (((varidx%26) + varoffset) < 26);
1987 varoffset++) {
1988 forloopcontext.variable[varidx + varoffset] = emptyW;
1991 /* Loop extracting the tokens
1992 Note: nexttoken of 0 means there were no tokens requested, to handle
1993 the special case of tokens=* */
1994 varoffset = 0;
1995 WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
1996 while (varidx >= 0 && (nexttoken > 0 && (nexttoken > lasttoken))) {
1997 anyduplicates |= thisduplicate;
1999 /* Extract the token number requested and set into the next variable context */
2000 parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims);
2001 WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
2002 varidx + varoffset, wine_dbgstr_w(parm));
2003 if (varidx >=0) {
2004 if (parm) forloopcontext.variable[varidx + varoffset] = xstrdupW(parm);
2005 varoffset++;
2006 if (((varidx%26)+varoffset) >= 26) break;
2009 /* Find the next token */
2010 lasttoken = nexttoken;
2011 nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
2012 &starfound, &thisduplicate);
2015 /* If all the rest of the tokens were requested, and there is still space in
2016 the variable range, write them now */
2017 if (!anyduplicates && starfound && varidx >= 0 && (((varidx%26) + varoffset) < 26)) {
2018 nexttoken++;
2019 WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
2020 WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
2021 varidx + varoffset, wine_dbgstr_w(parm));
2022 if (parm) forloopcontext.variable[varidx + varoffset] = xstrdupW(parm);
2025 /* Execute the body of the foor loop with these values */
2026 if (varidx >= 0 && forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
2027 CMD_LIST *thisCmdStart = cmdStart;
2028 *doExecuted = TRUE;
2029 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
2030 *cmdEnd = thisCmdStart;
2033 /* Free the duplicated strings, and restore the context */
2034 if (varidx >=0) {
2035 int i;
2036 for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
2037 if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
2038 (forloopcontext.variable[i] != emptyW)) {
2039 free(forloopcontext.variable[i]);
2044 /* Restore the original for variable contextx */
2045 forloopcontext = oldcontext;
2048 /**************************************************************************
2049 * WCMD_forf_getinput
2051 * Return a FILE* which can be used for reading the input lines,
2052 * either to a specific file (which may be quote delimited as we have to
2053 * read the parameters in raw mode) or to a command which we need to
2054 * execute. The command being executed runs in its own shell and stores
2055 * its data in a temporary file.
2057 * Parameters:
2058 * usebackq [I] - Indicates whether usebackq is in effect or not
2059 * itemStr [I] - The item to be handled, either a filename or
2060 * whole command string to execute
2061 * iscmd [I] - Identifies whether this is a command or not
2063 * Returns a file handle which can be used to read the input lines from.
2065 static FILE* WCMD_forf_getinput(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
2066 WCHAR temp_str[MAX_PATH];
2067 WCHAR temp_file[MAX_PATH];
2068 WCHAR temp_cmd[MAXSTRING];
2069 WCHAR *trimmed = NULL;
2070 FILE* ret;
2072 /* Remove leading and trailing character (but there may be trailing whitespace too) */
2073 if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
2074 (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
2075 (!iscmd && (itemstr[0] == '"' && usebackq)))
2077 trimmed = WCMD_strtrim(itemstr);
2078 if (trimmed) {
2079 itemstr = trimmed;
2081 itemstr[lstrlenW(itemstr)-1] = 0x00;
2082 itemstr++;
2085 if (iscmd) {
2086 /* Get temp filename */
2087 GetTempPathW(ARRAY_SIZE(temp_str), temp_str);
2088 GetTempFileNameW(temp_str, L"CMD", 0, temp_file);
2090 /* Redirect output to the temporary file */
2091 wsprintfW(temp_str, L">%s", temp_file);
2092 wsprintfW(temp_cmd, L"CMD.EXE /C %s", itemstr);
2093 WINE_TRACE("Issuing '%s' with redirs '%s'\n",
2094 wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
2095 WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
2097 /* Open the file, read line by line and process */
2098 ret = _wfopen(temp_file, L"rt,ccs=unicode");
2099 } else {
2100 /* Open the file, read line by line and process */
2101 WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
2102 ret = _wfopen(itemstr, L"rt,ccs=unicode");
2104 free(trimmed);
2105 return ret;
2108 /**************************************************************************
2109 * WCMD_for
2111 * Batch file loop processing.
2113 * On entry: cmdList contains the syntax up to the set
2114 * next cmdList and all in that bracket contain the set data
2115 * next cmdlist contains the DO cmd
2116 * following that is either brackets or && entries (as per if)
2120 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
2122 WIN32_FIND_DATAW fd;
2123 HANDLE hff;
2124 int i;
2125 const int in_len = lstrlenW(L"in");
2126 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
2127 WCHAR variable[4];
2128 int varidx = -1;
2129 WCHAR *oldvariablevalue;
2130 WCHAR *firstCmd;
2131 int thisDepth;
2132 WCHAR optionsRoot[MAX_PATH];
2133 DIRECTORY_STACK *dirsToWalk = NULL;
2134 BOOL expandDirs = FALSE;
2135 BOOL useNumbers = FALSE;
2136 BOOL doFileset = FALSE;
2137 BOOL doRecurse = FALSE;
2138 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
2139 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
2140 int itemNum;
2141 CMD_LIST *thisCmdStart;
2142 int parameterNo = 0;
2143 WCHAR forf_eol = 0;
2144 int forf_skip = 0;
2145 WCHAR forf_delims[256];
2146 WCHAR forf_tokens[MAXSTRING];
2147 BOOL forf_usebackq = FALSE;
2149 /* Handle optional qualifiers (multiple are allowed) */
2150 WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2152 optionsRoot[0] = 0;
2153 while (thisArg && *thisArg == '/') {
2154 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
2155 thisArg++;
2156 switch (towupper(*thisArg)) {
2157 case 'D': expandDirs = TRUE; break;
2158 case 'L': useNumbers = TRUE; break;
2160 /* Recursive is special case - /R can have an optional path following it */
2161 /* filenamesets are another special case - /F can have an optional options following it */
2162 case 'R':
2163 case 'F':
2165 /* When recursing directories, use current directory as the starting point unless
2166 subsequently overridden */
2167 doRecurse = (towupper(*thisArg) == 'R');
2168 if (doRecurse) GetCurrentDirectoryW(ARRAY_SIZE(optionsRoot), optionsRoot);
2170 doFileset = (towupper(*thisArg) == 'F');
2172 /* Retrieve next parameter to see if is root/options (raw form required
2173 with for /f, or unquoted in for /r) */
2174 thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
2176 /* Next parm is either qualifier, path/options or variable -
2177 only care about it if it is the path/options */
2178 if (thisArg && *thisArg != '/' && *thisArg != '%') {
2179 parameterNo++;
2180 lstrcpyW(optionsRoot, thisArg);
2182 break;
2184 default:
2185 WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
2188 /* Step to next token */
2189 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2192 /* Ensure line continues with variable */
2193 if (*thisArg != '%') {
2194 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2195 return;
2198 /* With for /f parse the options if provided */
2199 if (doFileset) {
2200 if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
2201 forf_delims, forf_tokens, &forf_usebackq))
2203 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2204 return;
2207 /* Set up the list of directories to recurse if we are going to */
2208 } else if (doRecurse) {
2209 /* Allocate memory, add to list */
2210 dirsToWalk = xalloc(sizeof(DIRECTORY_STACK));
2211 dirsToWalk->next = NULL;
2212 dirsToWalk->dirName = xstrdupW(optionsRoot);
2213 WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
2216 /* Variable should follow */
2217 lstrcpyW(variable, thisArg);
2218 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
2219 varidx = FOR_VAR_IDX(variable[1]);
2221 /* Ensure line continues with IN */
2222 thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
2223 if (!thisArg
2224 || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2225 thisArg, in_len, L"in", in_len) == CSTR_EQUAL)) {
2226 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2227 return;
2230 /* Save away where the set of data starts and the variable */
2231 thisDepth = (*cmdList)->bracketDepth;
2232 *cmdList = (*cmdList)->nextcommand;
2233 setStart = (*cmdList);
2235 /* Skip until the close bracket */
2236 WINE_TRACE("Searching %p as the set\n", *cmdList);
2237 while (*cmdList &&
2238 (*cmdList)->command != NULL &&
2239 (*cmdList)->bracketDepth > thisDepth) {
2240 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
2241 *cmdList = (*cmdList)->nextcommand;
2244 /* Skip the close bracket, if there is one */
2245 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
2247 /* Syntax error if missing close bracket, or nothing following it
2248 and once we have the complete set, we expect a DO */
2249 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
2250 if ((*cmdList == NULL) || !WCMD_keyword_ws_found(L"do", (*cmdList)->command)) {
2251 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
2252 return;
2255 cmdEnd = *cmdList;
2257 /* Loop repeatedly per-directory we are potentially walking, when in for /r
2258 mode, or once for the rest of the time. */
2259 do {
2261 /* Save away the starting position for the commands (and offset for the
2262 first one) */
2263 cmdStart = *cmdList;
2264 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
2265 itemNum = 0;
2267 /* If we are recursing directories (ie /R), add all sub directories now, then
2268 prefix the root when searching for the item */
2269 if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
2271 thisSet = setStart;
2272 /* Loop through all set entries */
2273 while (thisSet &&
2274 thisSet->command != NULL &&
2275 thisSet->bracketDepth >= thisDepth) {
2277 /* Loop through all entries on the same line */
2278 WCHAR *staticitem;
2279 WCHAR *itemStart;
2280 WCHAR buffer[MAXSTRING];
2282 WINE_TRACE("Processing for set %p\n", thisSet);
2283 i = 0;
2284 while (*(staticitem = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
2287 * If the parameter within the set has a wildcard then search for matching files
2288 * otherwise do a literal substitution.
2291 /* Take a copy of the item returned from WCMD_parameter as it is held in a
2292 static buffer which can be overwritten during parsing of the for body */
2293 WCHAR item[MAXSTRING];
2294 lstrcpyW(item, staticitem);
2296 thisCmdStart = cmdStart;
2298 itemNum++;
2299 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
2301 if (!useNumbers && !doFileset) {
2302 WCHAR fullitem[MAX_PATH];
2303 int prefixlen = 0;
2305 /* Now build the item to use / search for in the specified directory,
2306 as it is fully qualified in the /R case */
2307 if (dirsToWalk) {
2308 lstrcpyW(fullitem, dirsToWalk->dirName);
2309 lstrcatW(fullitem, L"\\");
2310 lstrcatW(fullitem, item);
2311 } else {
2312 WCHAR *prefix = wcsrchr(item, '\\');
2313 if (prefix) prefixlen = (prefix - item) + 1;
2314 lstrcpyW(fullitem, item);
2317 if (wcspbrk(fullitem, L"*?")) {
2318 hff = FindFirstFileW(fullitem, &fd);
2319 if (hff != INVALID_HANDLE_VALUE) {
2320 do {
2321 BOOL isDirectory = FALSE;
2323 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
2325 /* Handle as files or dirs appropriately, but ignore . and .. */
2326 if (isDirectory == expandDirs &&
2327 (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
2329 thisCmdStart = cmdStart;
2330 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
2332 if (doRecurse) {
2333 if (wcslen(dirsToWalk->dirName) + 1 + wcslen(fd.cFileName) >= MAX_PATH)
2335 WINE_TRACE("Skipping too long path %s\\%s\n",
2336 debugstr_w(dirsToWalk->dirName), debugstr_w(fd.cFileName));
2337 continue;
2339 lstrcpyW(fullitem, dirsToWalk->dirName);
2340 lstrcatW(fullitem, L"\\");
2341 lstrcatW(fullitem, fd.cFileName);
2342 } else {
2343 if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
2344 fullitem[prefixlen] = 0x00;
2345 lstrcatW(fullitem, fd.cFileName);
2347 doExecuted = TRUE;
2349 /* Save away any existing for variable context (e.g. nested for loops)
2350 and restore it after executing the body of this for loop */
2351 if (varidx >= 0) {
2352 oldvariablevalue = forloopcontext.variable[varidx];
2353 forloopcontext.variable[varidx] = fullitem;
2355 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2356 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2358 cmdEnd = thisCmdStart;
2360 } while (FindNextFileW(hff, &fd) != 0);
2361 FindClose (hff);
2363 } else {
2364 doExecuted = TRUE;
2366 /* Save away any existing for variable context (e.g. nested for loops)
2367 and restore it after executing the body of this for loop */
2368 if (varidx >= 0) {
2369 oldvariablevalue = forloopcontext.variable[varidx];
2370 forloopcontext.variable[varidx] = fullitem;
2372 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2373 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2375 cmdEnd = thisCmdStart;
2378 } else if (useNumbers) {
2379 /* Convert the first 3 numbers to signed longs and save */
2380 if (itemNum <=3) numbers[itemNum-1] = wcstol(item, NULL, 10);
2381 /* else ignore them! */
2383 /* Filesets - either a list of files, or a command to run and parse the output */
2384 } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
2385 (forf_usebackq && *itemStart != '\''))) {
2387 FILE *input;
2388 WCHAR *itemparm;
2390 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
2391 wine_dbgstr_w(item));
2393 /* If backquote or single quote, we need to launch that command
2394 and parse the results - use a temporary file */
2395 if ((forf_usebackq && *itemStart == '`') ||
2396 (!forf_usebackq && *itemStart == '\'')) {
2398 /* Use itemstart because the command is the whole set, not just the first token */
2399 itemparm = itemStart;
2400 } else {
2402 /* Use item because the file to process is just the first item in the set */
2403 itemparm = item;
2405 input = WCMD_forf_getinput(forf_usebackq, itemparm, (itemparm==itemStart));
2407 /* Process the input file */
2408 if (!input) {
2409 WCMD_print_error ();
2410 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
2411 errorlevel = 1;
2412 return; /* FOR loop aborts at first failure here */
2414 } else {
2416 /* Read line by line until end of file */
2417 while (fgetws(buffer, ARRAY_SIZE(buffer), input)) {
2418 size_t len = wcslen(buffer);
2419 /* Either our buffer isn't large enough to fit a full line, or there's a stray
2420 * '\0' in the buffer.
2422 if (!feof(input) && (len == 0 || (buffer[len - 1] != '\n' && buffer[len - 1] != '\r')))
2423 break;
2424 while (len && (buffer[len - 1] == '\n' || buffer[len - 1] == '\r'))
2425 buffer[--len] = L'\0';
2426 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2427 &forf_skip, forf_eol, forf_delims, forf_tokens);
2428 buffer[0] = 0;
2430 fclose (input);
2433 /* When we have processed the item as a whole command, abort future set processing */
2434 if (itemparm==itemStart) {
2435 thisSet = NULL;
2436 break;
2439 /* Filesets - A string literal */
2440 } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
2441 (forf_usebackq && *itemStart == '\''))) {
2443 /* Remove leading and trailing character, ready to parse with delims= delimiters
2444 Note that the last quote is removed from the set and the string terminates
2445 there to mimic windows */
2446 WCHAR *strend = wcsrchr(itemStart, forf_usebackq?'\'':'"');
2447 if (strend) {
2448 *strend = 0x00;
2449 itemStart++;
2452 /* Copy the item away from the global buffer used by WCMD_parameter */
2453 lstrcpyW(buffer, itemStart);
2454 WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
2455 &forf_skip, forf_eol, forf_delims, forf_tokens);
2457 /* Only one string can be supplied in the whole set, abort future set processing */
2458 thisSet = NULL;
2459 break;
2462 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
2463 i++;
2466 /* Move onto the next set line */
2467 if (thisSet) thisSet = thisSet->nextcommand;
2470 /* If /L is provided, now run the for loop */
2471 if (useNumbers) {
2472 WCHAR thisNum[20];
2474 WINE_TRACE("FOR /L provided range from %ld to %ld step %ld\n",
2475 numbers[0], numbers[2], numbers[1]);
2476 for (i=numbers[0];
2477 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
2478 i=i + numbers[1]) {
2480 swprintf(thisNum, ARRAY_SIZE(thisNum), L"%d", i);
2481 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
2483 thisCmdStart = cmdStart;
2484 doExecuted = TRUE;
2486 /* Save away any existing for variable context (e.g. nested for loops)
2487 and restore it after executing the body of this for loop */
2488 if (varidx >= 0) {
2489 oldvariablevalue = forloopcontext.variable[varidx];
2490 forloopcontext.variable[varidx] = thisNum;
2492 WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
2493 if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
2495 cmdEnd = thisCmdStart;
2498 /* If we are walking directories, move on to any which remain */
2499 if (dirsToWalk != NULL) {
2500 DIRECTORY_STACK *nextDir = dirsToWalk->next;
2501 free(dirsToWalk->dirName);
2502 free(dirsToWalk);
2503 dirsToWalk = nextDir;
2504 if (dirsToWalk) WINE_TRACE("Moving to next directory to iterate: %s\n",
2505 wine_dbgstr_w(dirsToWalk->dirName));
2506 else WINE_TRACE("Finished all directories.\n");
2509 } while (dirsToWalk != NULL);
2511 /* Now skip over the do part if we did not perform the for loop so far.
2512 We store in cmdEnd the next command after the do block, but we only
2513 know this if something was run. If it has not been, we need to calculate
2514 it. */
2515 if (!doExecuted) {
2516 thisCmdStart = cmdStart;
2517 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
2518 WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
2519 cmdEnd = thisCmdStart;
2522 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
2523 all processing, OR it should be pointing to the end of && processing OR
2524 it should be pointing at the NULL end of bracket for the DO. The return
2525 value needs to be the NEXT command to execute, which it either is, or
2526 we need to step over the closing bracket */
2527 *cmdList = cmdEnd;
2528 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
2531 /**************************************************************************
2532 * WCMD_give_help
2534 * Simple on-line help. Help text is stored in the resource file.
2537 void WCMD_give_help (const WCHAR *args)
2539 size_t i;
2541 args = WCMD_skip_leading_spaces((WCHAR*) args);
2542 if (!*args) {
2543 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
2545 else {
2546 /* Display help message for builtin commands */
2547 for (i=0; i<=WCMD_EXIT; i++) {
2548 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2549 args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
2550 WCMD_output_asis (WCMD_LoadMessage(i));
2551 return;
2554 /* Launch the command with the /? option for external commands shipped with cmd.exe */
2555 for (i = 0; i <= ARRAY_SIZE(externals); i++) {
2556 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2557 args, -1, externals[i], -1) == CSTR_EQUAL) {
2558 WCHAR cmd[128];
2559 lstrcpyW(cmd, args);
2560 lstrcatW(cmd, L" /?");
2561 WCMD_run_program(cmd, FALSE);
2562 return;
2565 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
2567 return;
2570 /****************************************************************************
2571 * WCMD_go_to
2573 * Batch file jump instruction. Not the most efficient algorithm ;-)
2574 * Prints error message if the specified label cannot be found - the file pointer is
2575 * then at EOF, effectively stopping the batch file.
2576 * FIXME: DOS is supposed to allow labels with spaces - we don't.
2579 void WCMD_goto (CMD_LIST **cmdList) {
2581 WCHAR string[MAX_PATH];
2582 WCHAR *labelend = NULL;
2583 const WCHAR labelEndsW[] = L"><|& :\t";
2585 /* Do not process any more parts of a processed multipart or multilines command */
2586 if (cmdList) *cmdList = NULL;
2588 if (context != NULL) {
2589 WCHAR *paramStart = param1, *str;
2591 if (param1[0] == 0x00) {
2592 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2593 return;
2596 /* Handle special :EOF label */
2597 if (lstrcmpiW(L":eof", param1) == 0) {
2598 context -> skip_rest = TRUE;
2599 return;
2602 /* Support goto :label as well as goto label plus remove trailing chars */
2603 if (*paramStart == ':') paramStart++;
2604 labelend = wcspbrk(paramStart, labelEndsW);
2605 if (labelend) *labelend = 0x00;
2606 WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
2608 /* Loop through potentially twice - once from current file position
2609 through to the end, and second time from start to current file
2610 position */
2611 if (*paramStart) {
2612 int loop;
2613 LARGE_INTEGER startli;
2614 for (loop=0; loop<2; loop++) {
2615 if (loop==0) {
2616 /* On first loop, save the file size */
2617 startli.QuadPart = 0;
2618 startli.u.LowPart = SetFilePointer(context -> h, startli.u.LowPart,
2619 &startli.u.HighPart, FILE_CURRENT);
2620 } else {
2621 /* On second loop, start at the beginning of the file */
2622 WINE_TRACE("Label not found, trying from beginning of file\n");
2623 if (loop==1) SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
2626 while (WCMD_fgets (string, ARRAY_SIZE(string), context -> h)) {
2627 str = string;
2629 /* Ignore leading whitespace or no-echo character */
2630 while (*str=='@' || iswspace (*str)) str++;
2632 /* If the first real character is a : then this is a label */
2633 if (*str == ':') {
2634 str++;
2636 /* Skip spaces between : and label */
2637 while (iswspace (*str)) str++;
2638 WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
2640 /* Label ends at whitespace or redirection characters */
2641 labelend = wcspbrk(str, labelEndsW);
2642 if (labelend) *labelend = 0x00;
2643 WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
2645 if (lstrcmpiW (str, paramStart) == 0) return;
2648 /* See if we have gone beyond the end point if second time through */
2649 if (loop==1) {
2650 LARGE_INTEGER curli;
2651 curli.QuadPart = 0;
2652 curli.u.LowPart = SetFilePointer(context -> h, curli.u.LowPart,
2653 &curli.u.HighPart, FILE_CURRENT);
2654 if (curli.QuadPart > startli.QuadPart) {
2655 WINE_TRACE("Reached wrap point, label not found\n");
2656 break;
2663 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
2664 context -> skip_rest = TRUE;
2666 return;
2669 /*****************************************************************************
2670 * WCMD_pushd
2672 * Push a directory onto the stack
2675 void WCMD_pushd (const WCHAR *args)
2677 struct env_stack *curdir;
2678 WCHAR *thisdir;
2680 if (wcschr(args, '/') != NULL) {
2681 SetLastError(ERROR_INVALID_PARAMETER);
2682 WCMD_print_error();
2683 return;
2686 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2687 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
2688 if( !curdir || !thisdir ) {
2689 LocalFree(curdir);
2690 LocalFree(thisdir);
2691 WINE_ERR ("out of memory\n");
2692 return;
2695 /* Change directory using CD code with /D parameter */
2696 lstrcpyW(quals, L"/D");
2697 GetCurrentDirectoryW (1024, thisdir);
2698 errorlevel = 0;
2699 WCMD_setshow_default(args);
2700 if (errorlevel) {
2701 LocalFree(curdir);
2702 LocalFree(thisdir);
2703 return;
2704 } else {
2705 curdir -> next = pushd_directories;
2706 curdir -> strings = thisdir;
2707 if (pushd_directories == NULL) {
2708 curdir -> u.stackdepth = 1;
2709 } else {
2710 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
2712 pushd_directories = curdir;
2717 /*****************************************************************************
2718 * WCMD_popd
2720 * Pop a directory from the stack
2723 void WCMD_popd (void) {
2724 struct env_stack *temp = pushd_directories;
2726 if (!pushd_directories)
2727 return;
2729 /* pop the old environment from the stack, and make it the current dir */
2730 pushd_directories = temp->next;
2731 SetCurrentDirectoryW(temp->strings);
2732 LocalFree (temp->strings);
2733 LocalFree (temp);
2736 /*******************************************************************
2737 * evaluate_if_comparison
2739 * Evaluates an "if" comparison operation
2741 * PARAMS
2742 * leftOperand [I] left operand, non NULL
2743 * operator [I] "if" binary comparison operator, non NULL
2744 * rightOperand [I] right operand, non NULL
2745 * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
2747 * RETURNS
2748 * Success: 1 if operator applied to the operands evaluates to TRUE
2749 * 0 if operator applied to the operands evaluates to FALSE
2750 * Failure: -1 if operator is not recognized
2752 static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
2753 const WCHAR *rightOperand, int caseInsensitive)
2755 WCHAR *endptr_leftOp, *endptr_rightOp;
2756 long int leftOperand_int, rightOperand_int;
2757 BOOL int_operands;
2759 /* == is a special case, as it always compares strings */
2760 if (!lstrcmpiW(operator, L"=="))
2761 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2762 : lstrcmpW (leftOperand, rightOperand) == 0;
2764 /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
2765 leftOperand_int = wcstol(leftOperand, &endptr_leftOp, 0);
2766 rightOperand_int = wcstol(rightOperand, &endptr_rightOp, 0);
2767 int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
2769 /* Perform actual (integer or string) comparison */
2770 if (!lstrcmpiW(operator, L"lss")) {
2771 if (int_operands)
2772 return leftOperand_int < rightOperand_int;
2773 else
2774 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
2775 : lstrcmpW (leftOperand, rightOperand) < 0;
2778 if (!lstrcmpiW(operator, L"leq")) {
2779 if (int_operands)
2780 return leftOperand_int <= rightOperand_int;
2781 else
2782 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
2783 : lstrcmpW (leftOperand, rightOperand) <= 0;
2786 if (!lstrcmpiW(operator, L"equ")) {
2787 if (int_operands)
2788 return leftOperand_int == rightOperand_int;
2789 else
2790 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
2791 : lstrcmpW (leftOperand, rightOperand) == 0;
2794 if (!lstrcmpiW(operator, L"neq")) {
2795 if (int_operands)
2796 return leftOperand_int != rightOperand_int;
2797 else
2798 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
2799 : lstrcmpW (leftOperand, rightOperand) != 0;
2802 if (!lstrcmpiW(operator, L"geq")) {
2803 if (int_operands)
2804 return leftOperand_int >= rightOperand_int;
2805 else
2806 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
2807 : lstrcmpW (leftOperand, rightOperand) >= 0;
2810 if (!lstrcmpiW(operator, L"gtr")) {
2811 if (int_operands)
2812 return leftOperand_int > rightOperand_int;
2813 else
2814 return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
2815 : lstrcmpW (leftOperand, rightOperand) > 0;
2818 return -1;
2821 int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate)
2823 WCHAR condition[MAX_PATH];
2824 int caseInsensitive = (wcsstr(quals, L"/I") != NULL);
2826 *negate = !lstrcmpiW(param1,L"not");
2827 lstrcpyW(condition, (*negate ? param2 : param1));
2828 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
2830 if (!lstrcmpiW(condition, L"errorlevel")) {
2831 WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
2832 WCHAR *endptr;
2833 long int param_int = wcstol(param, &endptr, 10);
2834 if (*endptr) goto syntax_err;
2835 *test = ((long int)errorlevel >= param_int);
2836 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2838 else if (!lstrcmpiW(condition, L"exist")) {
2839 WIN32_FIND_DATAW fd;
2840 HANDLE hff;
2841 WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
2842 int len = lstrlenW(param);
2844 if (!len) {
2845 *test = FALSE;
2846 } else {
2847 /* FindFirstFile does not like a directory path ending in '\' or '/', so append a '.' */
2848 if (param[len-1] == '\\' || param[len-1] == '/') wcscat(param, L".");
2850 hff = FindFirstFileW(param, &fd);
2851 *test = (hff != INVALID_HANDLE_VALUE);
2852 if (*test) FindClose(hff);
2855 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2857 else if (!lstrcmpiW(condition, L"defined")) {
2858 *test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE),
2859 NULL, 0) > 0);
2860 WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
2862 else { /* comparison operation */
2863 WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
2864 WCHAR *paramStart;
2866 lstrcpyW(leftOperand, WCMD_parameter(p, (*negate)+caseInsensitive, &paramStart, TRUE, FALSE));
2867 if (!*leftOperand)
2868 goto syntax_err;
2870 /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
2871 p = paramStart + lstrlenW(leftOperand);
2872 while (*p == ' ' || *p == '\t')
2873 p++;
2875 if (!wcsncmp(p, L"==", lstrlenW(L"==")))
2876 lstrcpyW(operator, L"==");
2877 else {
2878 lstrcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
2879 if (!*operator) goto syntax_err;
2881 p += lstrlenW(operator);
2883 lstrcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
2884 if (!*rightOperand)
2885 goto syntax_err;
2887 *test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
2888 if (*test == -1)
2889 goto syntax_err;
2891 p = paramStart + lstrlenW(rightOperand);
2892 WCMD_parameter(p, 0, command, FALSE, FALSE);
2895 return 1;
2897 syntax_err:
2898 return -1;
2901 /****************************************************************************
2902 * WCMD_if
2904 * Batch file conditional.
2906 * On entry, cmdlist will point to command containing the IF, and optionally
2907 * the first command to execute (if brackets not found)
2908 * If &&'s were found, this may be followed by a record flagged as isAmpersand
2909 * If ('s were found, execute all within that bracket
2910 * Command may optionally be followed by an ELSE - need to skip instructions
2911 * in the else using the same logic
2913 * FIXME: Much more syntax checking needed!
2915 void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
2917 int negate; /* Negate condition */
2918 int test; /* Condition evaluation result */
2919 WCHAR *command;
2921 /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
2922 set in a call to WCMD_parse before */
2923 if (evaluate_if_condition(p, &command, &test, &negate) == -1)
2924 goto syntax_err;
2926 WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n",
2927 wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
2928 wine_dbgstr_w(param2), wine_dbgstr_w(command));
2930 /* Process rest of IF statement which is on the same line
2931 Note: This may process all or some of the cmdList (eg a GOTO) */
2932 WCMD_part_execute(cmdList, command, TRUE, (test != negate));
2933 return;
2935 syntax_err:
2936 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2939 /****************************************************************************
2940 * WCMD_move
2942 * Move a file, directory tree or wildcarded set of files.
2945 void WCMD_move (void)
2947 BOOL status;
2948 WIN32_FIND_DATAW fd;
2949 HANDLE hff;
2950 WCHAR input[MAX_PATH];
2951 WCHAR output[MAX_PATH];
2952 WCHAR drive[10];
2953 WCHAR dir[MAX_PATH];
2954 WCHAR fname[MAX_PATH];
2955 WCHAR ext[MAX_PATH];
2957 if (param1[0] == 0x00) {
2958 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2959 return;
2962 /* If no destination supplied, assume current directory */
2963 if (param2[0] == 0x00) {
2964 lstrcpyW(param2, L".");
2967 /* If 2nd parm is directory, then use original filename */
2968 /* Convert partial path to full path */
2969 if (!WCMD_get_fullpath(param1, ARRAY_SIZE(input), input, NULL) ||
2970 !WCMD_get_fullpath(param2, ARRAY_SIZE(output), output, NULL)) return;
2971 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
2972 wine_dbgstr_w(param1), wine_dbgstr_w(output));
2974 /* Split into components */
2975 _wsplitpath(input, drive, dir, fname, ext);
2977 hff = FindFirstFileW(input, &fd);
2978 if (hff == INVALID_HANDLE_VALUE)
2979 return;
2981 do {
2982 WCHAR dest[MAX_PATH];
2983 WCHAR src[MAX_PATH];
2984 DWORD attribs;
2985 BOOL ok = TRUE;
2986 DWORD flags = 0;
2988 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
2990 /* Build src & dest name */
2991 lstrcpyW(src, drive);
2992 lstrcatW(src, dir);
2994 /* See if dest is an existing directory */
2995 attribs = GetFileAttributesW(output);
2996 if (attribs != INVALID_FILE_ATTRIBUTES &&
2997 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
2998 lstrcpyW(dest, output);
2999 lstrcatW(dest, L"\\");
3000 lstrcatW(dest, fd.cFileName);
3001 } else {
3002 lstrcpyW(dest, output);
3005 lstrcatW(src, fd.cFileName);
3007 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3008 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3010 /* If destination exists, prompt unless /Y supplied */
3011 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
3012 BOOL force = FALSE;
3013 WCHAR copycmd[MAXSTRING];
3014 DWORD len;
3016 /* Default whether automatic overwriting is on. If we are interactive then
3017 we prompt by default, otherwise we overwrite by default
3018 /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
3019 if (wcsstr(quals, L"/-Y"))
3020 force = FALSE;
3021 else if (wcsstr(quals, L"/Y"))
3022 force = TRUE;
3023 else {
3024 /* By default, we will force the overwrite in batch mode and ask for
3025 * confirmation in interactive mode. */
3026 force = !interactive;
3027 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
3028 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
3029 * default behavior. */
3030 len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
3031 if (len && len < ARRAY_SIZE(copycmd)) {
3032 if (!lstrcmpiW(copycmd, L"/Y"))
3033 force = TRUE;
3034 else if (!lstrcmpiW(copycmd, L"/-Y"))
3035 force = FALSE;
3039 /* Prompt if overwriting */
3040 if (!force) {
3041 WCHAR* question;
3043 /* Ask for confirmation */
3044 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
3045 ok = WCMD_ask_confirm(question, FALSE, NULL);
3046 LocalFree(question);
3049 if (ok)
3050 flags |= MOVEFILE_REPLACE_EXISTING;
3053 if (ok) {
3054 status = MoveFileExW(src, dest, flags);
3055 } else {
3056 status = TRUE;
3059 if (!status) {
3060 WCMD_print_error ();
3061 errorlevel = 1;
3063 } while (FindNextFileW(hff, &fd) != 0);
3065 FindClose(hff);
3068 /****************************************************************************
3069 * WCMD_pause
3071 * Suspend execution of a batch script until a key is typed
3074 void WCMD_pause (void)
3076 DWORD oldmode;
3077 BOOL have_console;
3078 DWORD count;
3079 WCHAR key;
3080 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
3082 have_console = GetConsoleMode(hIn, &oldmode);
3083 if (have_console)
3084 SetConsoleMode(hIn, 0);
3086 WCMD_output_asis(anykey);
3087 WCMD_ReadFile(hIn, &key, 1, &count);
3088 if (have_console)
3089 SetConsoleMode(hIn, oldmode);
3092 /****************************************************************************
3093 * WCMD_remove_dir
3095 * Delete a directory.
3098 void WCMD_remove_dir (WCHAR *args) {
3100 int argno = 0;
3101 int argsProcessed = 0;
3102 WCHAR *argN = args;
3104 /* Loop through all args */
3105 while (argN) {
3106 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
3107 if (argN && argN[0] != '/') {
3108 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
3109 wine_dbgstr_w(quals));
3110 argsProcessed++;
3112 /* If subdirectory search not supplied, just try to remove
3113 and report error if it fails (eg if it contains a file) */
3114 if (wcsstr(quals, L"/S") == NULL) {
3115 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
3117 /* Otherwise use ShFileOp to recursively remove a directory */
3118 } else {
3120 SHFILEOPSTRUCTW lpDir;
3122 /* Ask first */
3123 if (wcsstr(quals, L"/Q") == NULL) {
3124 BOOL ok;
3125 WCHAR question[MAXSTRING];
3127 /* Ask for confirmation */
3128 wsprintfW(question, L"%s ", thisArg);
3129 ok = WCMD_ask_confirm(question, TRUE, NULL);
3131 /* Abort if answer is 'N' */
3132 if (!ok) return;
3135 /* Do the delete */
3136 lpDir.hwnd = NULL;
3137 lpDir.pTo = NULL;
3138 lpDir.pFrom = thisArg;
3139 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
3140 lpDir.wFunc = FO_DELETE;
3142 /* SHFileOperationW needs file list with a double null termination */
3143 thisArg[lstrlenW(thisArg) + 1] = 0x00;
3145 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
3150 /* Handle no valid args */
3151 if (argsProcessed == 0) {
3152 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3153 return;
3158 /****************************************************************************
3159 * WCMD_rename
3161 * Rename a file.
3164 void WCMD_rename (void)
3166 BOOL status;
3167 HANDLE hff;
3168 WIN32_FIND_DATAW fd;
3169 WCHAR input[MAX_PATH];
3170 WCHAR *dotDst = NULL;
3171 WCHAR drive[10];
3172 WCHAR dir[MAX_PATH];
3173 WCHAR fname[MAX_PATH];
3174 WCHAR ext[MAX_PATH];
3176 errorlevel = 0;
3178 /* Must be at least two args */
3179 if (param1[0] == 0x00 || param2[0] == 0x00) {
3180 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
3181 errorlevel = 1;
3182 return;
3185 /* Destination cannot contain a drive letter or directory separator */
3186 if ((wcschr(param2,':') != NULL) || (wcschr(param2,'\\') != NULL)) {
3187 SetLastError(ERROR_INVALID_PARAMETER);
3188 WCMD_print_error();
3189 errorlevel = 1;
3190 return;
3193 /* Convert partial path to full path */
3194 if (!WCMD_get_fullpath(param1, ARRAY_SIZE(input), input, NULL)) return;
3195 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
3196 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
3197 dotDst = wcschr(param2, '.');
3199 /* Split into components */
3200 _wsplitpath(input, drive, dir, fname, ext);
3202 hff = FindFirstFileW(input, &fd);
3203 if (hff == INVALID_HANDLE_VALUE)
3204 return;
3206 do {
3207 WCHAR dest[MAX_PATH];
3208 WCHAR src[MAX_PATH];
3209 WCHAR *dotSrc = NULL;
3210 int dirLen;
3212 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
3214 /* FIXME: If dest name or extension is *, replace with filename/ext
3215 part otherwise use supplied name. This supports:
3216 ren *.fred *.jim
3217 ren jim.* fred.* etc
3218 However, windows has a more complex algorithm supporting eg
3219 ?'s and *'s mid name */
3220 dotSrc = wcschr(fd.cFileName, '.');
3222 /* Build src & dest name */
3223 lstrcpyW(src, drive);
3224 lstrcatW(src, dir);
3225 lstrcpyW(dest, src);
3226 dirLen = lstrlenW(src);
3227 lstrcatW(src, fd.cFileName);
3229 /* Build name */
3230 if (param2[0] == '*') {
3231 lstrcatW(dest, fd.cFileName);
3232 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
3233 } else {
3234 lstrcatW(dest, param2);
3235 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
3238 /* Build Extension */
3239 if (dotDst && (*(dotDst+1)=='*')) {
3240 if (dotSrc) lstrcatW(dest, dotSrc);
3241 } else if (dotDst) {
3242 lstrcatW(dest, dotDst);
3245 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
3246 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
3248 status = MoveFileW(src, dest);
3250 if (!status) {
3251 WCMD_print_error ();
3252 errorlevel = 1;
3254 } while (FindNextFileW(hff, &fd) != 0);
3256 FindClose(hff);
3259 /*****************************************************************************
3260 * WCMD_dupenv
3262 * Make a copy of the environment.
3264 static WCHAR *WCMD_dupenv( const WCHAR *env )
3266 WCHAR *env_copy;
3267 int len;
3269 if( !env )
3270 return NULL;
3272 len = 0;
3273 while ( env[len] )
3274 len += (lstrlenW(&env[len]) + 1);
3276 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
3277 if (!env_copy)
3279 WINE_ERR("out of memory\n");
3280 return env_copy;
3282 memcpy (env_copy, env, len*sizeof (WCHAR));
3283 env_copy[len] = 0;
3285 return env_copy;
3288 /*****************************************************************************
3289 * WCMD_setlocal
3291 * setlocal pushes the environment onto a stack
3292 * Save the environment as unicode so we don't screw anything up.
3294 void WCMD_setlocal (const WCHAR *s) {
3295 WCHAR *env;
3296 struct env_stack *env_copy;
3297 WCHAR cwd[MAX_PATH];
3298 BOOL newdelay;
3300 /* setlocal does nothing outside of batch programs */
3301 if (!context) return;
3303 /* DISABLEEXTENSIONS ignored */
3305 /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
3306 (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
3307 if (!wcsicmp(param1, L"ENABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"ENABLEDELAYEDEXPANSION")) {
3308 newdelay = TRUE;
3309 } else if (!wcsicmp(param1, L"DISABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"DISABLEDELAYEDEXPANSION")) {
3310 newdelay = FALSE;
3311 } else {
3312 newdelay = delayedsubst;
3314 WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
3316 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
3317 if( !env_copy )
3319 WINE_ERR ("out of memory\n");
3320 return;
3323 env = GetEnvironmentStringsW ();
3324 env_copy->strings = WCMD_dupenv (env);
3325 if (env_copy->strings)
3327 env_copy->batchhandle = context->h;
3328 env_copy->next = saved_environment;
3329 env_copy->delayedsubst = delayedsubst;
3330 delayedsubst = newdelay;
3331 saved_environment = env_copy;
3333 /* Save the current drive letter */
3334 GetCurrentDirectoryW(MAX_PATH, cwd);
3335 env_copy->u.cwd = cwd[0];
3337 else
3338 LocalFree (env_copy);
3340 FreeEnvironmentStringsW (env);
3344 /*****************************************************************************
3345 * WCMD_endlocal
3347 * endlocal pops the environment off a stack
3348 * Note: When searching for '=', search from WCHAR position 1, to handle
3349 * special internal environment variables =C:, =D: etc
3351 void WCMD_endlocal (void) {
3352 WCHAR *env, *old, *p;
3353 struct env_stack *temp;
3354 int len, n;
3356 /* setlocal does nothing outside of batch programs */
3357 if (!context) return;
3359 /* setlocal needs a saved environment from within the same context (batch
3360 program) as it was saved in */
3361 if (!saved_environment || saved_environment->batchhandle != context->h)
3362 return;
3364 /* pop the old environment from the stack */
3365 temp = saved_environment;
3366 saved_environment = temp->next;
3368 /* delete the current environment, totally */
3369 env = GetEnvironmentStringsW ();
3370 old = WCMD_dupenv (env);
3371 len = 0;
3372 while (old[len]) {
3373 n = lstrlenW(&old[len]) + 1;
3374 p = wcschr(&old[len] + 1, '=');
3375 if (p)
3377 *p++ = 0;
3378 SetEnvironmentVariableW (&old[len], NULL);
3380 len += n;
3382 LocalFree (old);
3383 FreeEnvironmentStringsW (env);
3385 /* restore old environment */
3386 env = temp->strings;
3387 len = 0;
3388 delayedsubst = temp->delayedsubst;
3389 WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
3390 while (env[len]) {
3391 n = lstrlenW(&env[len]) + 1;
3392 p = wcschr(&env[len] + 1, '=');
3393 if (p)
3395 *p++ = 0;
3396 SetEnvironmentVariableW (&env[len], p);
3398 len += n;
3401 /* Restore current drive letter */
3402 if (IsCharAlphaW(temp->u.cwd)) {
3403 WCHAR envvar[4];
3404 WCHAR cwd[MAX_PATH];
3406 wsprintfW(envvar, L"=%c:", temp->u.cwd);
3407 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
3408 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
3409 SetCurrentDirectoryW(cwd);
3413 LocalFree (env);
3414 LocalFree (temp);
3417 /*****************************************************************************
3418 * WCMD_setshow_default
3420 * Set/Show the current default directory
3423 void WCMD_setshow_default (const WCHAR *args) {
3425 BOOL status;
3426 WCHAR string[1024];
3427 WCHAR cwd[1024];
3428 WCHAR *pos;
3429 WIN32_FIND_DATAW fd;
3430 HANDLE hff;
3432 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
3434 /* Skip /D and trailing whitespace if on the front of the command line */
3435 if (lstrlenW(args) >= 2 &&
3436 CompareStringW(LOCALE_USER_DEFAULT,
3437 NORM_IGNORECASE | SORT_STRINGSORT,
3438 args, 2, L"/D", -1) == CSTR_EQUAL) {
3439 args += 2;
3440 while (*args && (*args==' ' || *args=='\t'))
3441 args++;
3444 GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
3446 if (!*args) {
3447 lstrcatW(cwd, L"\r\n");
3448 WCMD_output_asis (cwd);
3450 else {
3451 /* Remove any double quotes, which may be in the
3452 middle, eg. cd "C:\Program Files"\Microsoft is ok */
3453 pos = string;
3454 while (*args) {
3455 if (*args != '"') *pos++ = *args;
3456 args++;
3458 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
3459 pos--;
3460 *pos = 0x00;
3462 /* Search for appropriate directory */
3463 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
3464 hff = FindFirstFileW(string, &fd);
3465 if (hff != INVALID_HANDLE_VALUE) {
3466 do {
3467 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
3468 WCHAR fpath[MAX_PATH];
3469 WCHAR drive[10];
3470 WCHAR dir[MAX_PATH];
3471 WCHAR fname[MAX_PATH];
3472 WCHAR ext[MAX_PATH];
3474 /* Convert path into actual directory spec */
3475 if (!WCMD_get_fullpath(string, ARRAY_SIZE(fpath), fpath, NULL)) return;
3476 _wsplitpath(fpath, drive, dir, fname, ext);
3478 /* Rebuild path */
3479 wsprintfW(string, L"%s%s%s", drive, dir, fd.cFileName);
3480 break;
3482 } while (FindNextFileW(hff, &fd) != 0);
3483 FindClose(hff);
3486 /* Change to that directory */
3487 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
3489 status = SetCurrentDirectoryW(string);
3490 if (!status) {
3491 errorlevel = 1;
3492 WCMD_print_error ();
3493 return;
3494 } else {
3496 /* Save away the actual new directory, to store as current location */
3497 GetCurrentDirectoryW(ARRAY_SIZE(string), string);
3499 /* Restore old directory if drive letter would change, and
3500 CD x:\directory /D (or pushd c:\directory) not supplied */
3501 if ((wcsstr(quals, L"/D") == NULL) &&
3502 (param1[1] == ':') && (towupper(param1[0]) != towupper(cwd[0]))) {
3503 SetCurrentDirectoryW(cwd);
3507 /* Set special =C: type environment variable, for drive letter of
3508 change of directory, even if path was restored due to missing
3509 /D (allows changing drive letter when not resident on that
3510 drive */
3511 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
3512 WCHAR env[4];
3513 lstrcpyW(env, L"=");
3514 memcpy(env+1, string, 2 * sizeof(WCHAR));
3515 env[3] = 0x00;
3516 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
3517 SetEnvironmentVariableW(env, string);
3521 return;
3524 /****************************************************************************
3525 * WCMD_setshow_date
3527 * Set/Show the system date
3528 * FIXME: Can't change date yet
3531 void WCMD_setshow_date (void) {
3533 WCHAR curdate[64], buffer[64];
3534 DWORD count;
3536 if (!*param1) {
3537 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, curdate, ARRAY_SIZE(curdate))) {
3538 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
3539 if (wcsstr(quals, L"/T") == NULL) {
3540 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
3541 if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count) &&
3542 count > 2) {
3543 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3547 else WCMD_print_error ();
3549 else {
3550 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
3554 /****************************************************************************
3555 * WCMD_compare
3556 * Note: Native displays 'fred' before 'fred ', so need to only compare up to
3557 * the equals sign.
3559 static int __cdecl WCMD_compare( const void *a, const void *b )
3561 int r;
3562 const WCHAR * const *str_a = a, * const *str_b = b;
3563 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
3564 *str_a, wcscspn(*str_a, L"="), *str_b, wcscspn(*str_b, L"=") );
3565 if( r == CSTR_LESS_THAN ) return -1;
3566 if( r == CSTR_GREATER_THAN ) return 1;
3567 return 0;
3570 /****************************************************************************
3571 * WCMD_setshow_sortenv
3573 * sort variables into order for display
3574 * Optionally only display those who start with a stub
3575 * returns the count displayed
3577 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
3579 UINT count=0, len=0, i, displayedcount=0, stublen=0;
3580 const WCHAR **str;
3582 if (stub) stublen = lstrlenW(stub);
3584 /* count the number of strings, and the total length */
3585 while ( s[len] ) {
3586 len += (lstrlenW(&s[len]) + 1);
3587 count++;
3590 /* add the strings to an array */
3591 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
3592 if( !str )
3593 return 0;
3594 str[0] = s;
3595 for( i=1; i<count; i++ )
3596 str[i] = str[i-1] + lstrlenW(str[i-1]) + 1;
3598 /* sort the array */
3599 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
3601 /* print it */
3602 for( i=0; i<count; i++ ) {
3603 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
3604 NORM_IGNORECASE | SORT_STRINGSORT,
3605 str[i], stublen, stub, -1) == CSTR_EQUAL) {
3606 /* Don't display special internal variables */
3607 if (str[i][0] != '=') {
3608 WCMD_output_asis(str[i]);
3609 WCMD_output_asis(L"\r\n");
3610 displayedcount++;
3615 LocalFree( str );
3616 return displayedcount;
3619 /****************************************************************************
3620 * WCMD_getprecedence
3621 * Return the precedence of a particular operator
3623 static int WCMD_getprecedence(const WCHAR in)
3625 switch (in) {
3626 case '!':
3627 case '~':
3628 case OP_POSITIVE:
3629 case OP_NEGATIVE:
3630 return 8;
3631 case '*':
3632 case '/':
3633 case '%':
3634 return 7;
3635 case '+':
3636 case '-':
3637 return 6;
3638 case '<':
3639 case '>':
3640 return 5;
3641 case '&':
3642 return 4;
3643 case '^':
3644 return 3;
3645 case '|':
3646 return 2;
3647 case '=':
3648 case OP_ASSSIGNMUL:
3649 case OP_ASSSIGNDIV:
3650 case OP_ASSSIGNMOD:
3651 case OP_ASSSIGNADD:
3652 case OP_ASSSIGNSUB:
3653 case OP_ASSSIGNAND:
3654 case OP_ASSSIGNNOT:
3655 case OP_ASSSIGNOR:
3656 case OP_ASSSIGNSHL:
3657 case OP_ASSSIGNSHR:
3658 return 1;
3659 default:
3660 return 0;
3664 /****************************************************************************
3665 * WCMD_pushnumber
3666 * Push either a number or name (environment variable) onto the supplied
3667 * stack
3669 static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
3670 VARSTACK *thisstack = xalloc(sizeof(VARSTACK));
3671 thisstack->isnum = (var == NULL);
3672 if (var) {
3673 thisstack->variable = var;
3674 WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
3675 } else {
3676 thisstack->value = num;
3677 WINE_TRACE("Pushed number %d\n", num);
3679 thisstack->next = *varstack;
3680 *varstack = thisstack;
3683 /****************************************************************************
3684 * WCMD_peeknumber
3685 * Returns the value of the top number or environment variable on the stack
3686 * and leaves the item on the stack.
3688 static int WCMD_peeknumber(VARSTACK **varstack) {
3689 int result = 0;
3690 VARSTACK *thisvar;
3692 if (varstack) {
3693 thisvar = *varstack;
3694 if (!thisvar->isnum) {
3695 WCHAR tmpstr[MAXSTRING];
3696 if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
3697 result = wcstol(tmpstr,NULL,0);
3699 WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
3700 } else {
3701 result = thisvar->value;
3704 WINE_TRACE("Peeked number %d\n", result);
3705 return result;
3708 /****************************************************************************
3709 * WCMD_popnumber
3710 * Returns the value of the top number or environment variable on the stack
3711 * and removes the item from the stack.
3713 static int WCMD_popnumber(VARSTACK **varstack) {
3714 int result = 0;
3715 VARSTACK *thisvar;
3717 if (varstack) {
3718 thisvar = *varstack;
3719 result = WCMD_peeknumber(varstack);
3720 if (!thisvar->isnum) free(thisvar->variable);
3721 *varstack = thisvar->next;
3722 free(thisvar);
3724 WINE_TRACE("Popped number %d\n", result);
3725 return result;
3728 /****************************************************************************
3729 * WCMD_pushoperator
3730 * Push an operator onto the supplied stack
3732 static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
3733 OPSTACK *thisstack = xalloc(sizeof(OPSTACK));
3734 thisstack->precedence = precedence;
3735 thisstack->op = op;
3736 thisstack->next = *opstack;
3737 WINE_TRACE("Pushed operator %c\n", op);
3738 *opstack = thisstack;
3741 /****************************************************************************
3742 * WCMD_popoperator
3743 * Returns the operator from the top of the stack and removes the item from
3744 * the stack.
3746 static WCHAR WCMD_popoperator(OPSTACK **opstack) {
3747 WCHAR result = 0;
3748 OPSTACK *thisop;
3750 if (opstack) {
3751 thisop = *opstack;
3752 result = thisop->op;
3753 *opstack = thisop->next;
3754 free(thisop);
3756 WINE_TRACE("Popped operator %c\n", result);
3757 return result;
3760 /****************************************************************************
3761 * WCMD_reduce
3762 * Actions the top operator on the stack against the first and sometimes
3763 * second value on the variable stack, and pushes the result
3764 * Returns non-zero on error.
3766 static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
3767 WCHAR thisop;
3768 int var1,var2;
3769 int rc = 0;
3771 if (!*opstack || !*varstack) {
3772 WINE_TRACE("No operators for the reduce\n");
3773 return WCMD_NOOPERATOR;
3776 /* Remove the top operator */
3777 thisop = WCMD_popoperator(opstack);
3778 WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
3780 /* One variable operators */
3781 var1 = WCMD_popnumber(varstack);
3782 switch (thisop) {
3783 case '!': WCMD_pushnumber(NULL, !var1, varstack);
3784 break;
3785 case '~': WCMD_pushnumber(NULL, ~var1, varstack);
3786 break;
3787 case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
3788 break;
3789 case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
3790 break;
3793 /* Two variable operators */
3794 if (!*varstack) {
3795 WINE_TRACE("No operands left for the reduce?\n");
3796 return WCMD_NOOPERAND;
3798 switch (thisop) {
3799 case '!':
3800 case '~':
3801 case OP_POSITIVE:
3802 case OP_NEGATIVE:
3803 break; /* Handled above */
3804 case '*': var2 = WCMD_popnumber(varstack);
3805 WCMD_pushnumber(NULL, var2*var1, varstack);
3806 break;
3807 case '/': var2 = WCMD_popnumber(varstack);
3808 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3809 WCMD_pushnumber(NULL, var2/var1, varstack);
3810 break;
3811 case '+': var2 = WCMD_popnumber(varstack);
3812 WCMD_pushnumber(NULL, var2+var1, varstack);
3813 break;
3814 case '-': var2 = WCMD_popnumber(varstack);
3815 WCMD_pushnumber(NULL, var2-var1, varstack);
3816 break;
3817 case '&': var2 = WCMD_popnumber(varstack);
3818 WCMD_pushnumber(NULL, var2&var1, varstack);
3819 break;
3820 case '%': var2 = WCMD_popnumber(varstack);
3821 if (var1 == 0) return WCMD_DIVIDEBYZERO;
3822 WCMD_pushnumber(NULL, var2%var1, varstack);
3823 break;
3824 case '^': var2 = WCMD_popnumber(varstack);
3825 WCMD_pushnumber(NULL, var2^var1, varstack);
3826 break;
3827 case '<': var2 = WCMD_popnumber(varstack);
3828 /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
3829 which differs from the compiler (for example gcc) so being explicit. */
3830 if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
3831 WCMD_pushnumber(NULL, 0, varstack);
3832 } else {
3833 WCMD_pushnumber(NULL, var2<<var1, varstack);
3835 break;
3836 case '>': var2 = WCMD_popnumber(varstack);
3837 WCMD_pushnumber(NULL, var2>>var1, varstack);
3838 break;
3839 case '|': var2 = WCMD_popnumber(varstack);
3840 WCMD_pushnumber(NULL, var2|var1, varstack);
3841 break;
3843 case OP_ASSSIGNMUL:
3844 case OP_ASSSIGNDIV:
3845 case OP_ASSSIGNMOD:
3846 case OP_ASSSIGNADD:
3847 case OP_ASSSIGNSUB:
3848 case OP_ASSSIGNAND:
3849 case OP_ASSSIGNNOT:
3850 case OP_ASSSIGNOR:
3851 case OP_ASSSIGNSHL:
3852 case OP_ASSSIGNSHR:
3854 int i = 0;
3856 /* The left of an equals must be one variable */
3857 if (!(*varstack) || (*varstack)->isnum) {
3858 return WCMD_NOOPERAND;
3861 /* Make the number stack grow by inserting the value of the variable */
3862 var2 = WCMD_peeknumber(varstack);
3863 WCMD_pushnumber(NULL, var2, varstack);
3864 WCMD_pushnumber(NULL, var1, varstack);
3866 /* Make the operand stack grow by pushing the assign operator plus the
3867 operator to perform */
3868 while (calcassignments[i].op != ' ' &&
3869 calcassignments[i].calculatedop != thisop) {
3870 i++;
3872 if (calcassignments[i].calculatedop == ' ') {
3873 WINE_ERR("Unexpected operator %c\n", thisop);
3874 return WCMD_NOOPERATOR;
3876 WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
3877 WCMD_pushoperator(calcassignments[i].op,
3878 WCMD_getprecedence(calcassignments[i].op), opstack);
3879 break;
3882 case '=':
3884 WCHAR result[MAXSTRING];
3886 /* Build the result, then push it onto the stack */
3887 swprintf(result, ARRAY_SIZE(result), L"%d", var1);
3888 WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
3889 wine_dbgstr_w(result));
3890 SetEnvironmentVariableW((*varstack)->variable, result);
3891 var2 = WCMD_popnumber(varstack);
3892 WCMD_pushnumber(NULL, var1, varstack);
3893 break;
3896 default: WINE_ERR("Unrecognized operator %c\n", thisop);
3899 return rc;
3903 /****************************************************************************
3904 * WCMD_handleExpression
3905 * Handles an expression provided to set /a - If it finds brackets, it uses
3906 * recursion to process the parts in brackets.
3908 static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
3910 static const WCHAR mathDelims[] = L" \t()!~-*/%+<>&^|=,";
3911 int rc = 0;
3912 WCHAR *pos;
3913 BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
3914 OPSTACK *opstackhead = NULL;
3915 VARSTACK *varstackhead = NULL;
3916 WCHAR foundhalf = 0;
3918 /* Initialize */
3919 WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
3920 pos = *expr;
3922 /* Iterate through until whole expression is processed */
3923 while (pos && *pos) {
3924 BOOL treatasnumber;
3926 /* Skip whitespace to get to the next character to process*/
3927 while (*pos && (*pos==' ' || *pos=='\t')) pos++;
3928 if (!*pos) goto exprreturn;
3930 /* If we have found anything other than an operator then it's a number/variable */
3931 if (wcschr(mathDelims, *pos) == NULL) {
3932 WCHAR *parmstart, *parm, *dupparm;
3933 WCHAR *nextpos;
3935 /* Cannot have an expression with var/number twice, without an operator
3936 in-between, nor or number following a half constructed << or >> operator */
3937 if (lastwasnumber || foundhalf) {
3938 rc = WCMD_NOOPERATOR;
3939 goto exprerrorreturn;
3941 lastwasnumber = TRUE;
3943 if (iswdigit(*pos)) {
3944 /* For a number - just push it onto the stack */
3945 int num = wcstoul(pos, &nextpos, 0);
3946 WCMD_pushnumber(NULL, num, &varstackhead);
3947 pos = nextpos;
3949 /* Verify the number was validly formed */
3950 if (*nextpos && (wcschr(mathDelims, *nextpos) == NULL)) {
3951 rc = WCMD_BADHEXOCT;
3952 goto exprerrorreturn;
3954 } else {
3956 /* For a variable - just push it onto the stack */
3957 parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
3958 dupparm = xstrdupW(parm);
3959 WCMD_pushnumber(dupparm, 0, &varstackhead);
3960 pos = parmstart + lstrlenW(dupparm);
3962 continue;
3965 /* We have found an operator. Some operators are one character, some two, and the minus
3966 and plus signs need special processing as they can be either operators or just influence
3967 the parameter which follows them */
3968 if (foundhalf && (*pos != foundhalf)) {
3969 /* Badly constructed operator pair */
3970 rc = WCMD_NOOPERATOR;
3971 goto exprerrorreturn;
3974 treatasnumber = FALSE; /* We are processing an operand */
3975 switch (*pos) {
3977 /* > and < are special as they are double character operators (and spaces can be between them!)
3978 If we see these for the first time, set a flag, and second time around we continue.
3979 Note these double character operators are stored as just one of the characters on the stack */
3980 case '>':
3981 case '<': if (!foundhalf) {
3982 foundhalf = *pos;
3983 pos++;
3984 break;
3986 /* We have found the rest, so clear up the knowledge of the half completed part and
3987 drop through to normal operator processing */
3988 foundhalf = 0;
3989 /* drop through */
3991 case '=': if (*pos=='=') {
3992 /* = is special cased as if the last was an operator then we may have e.g. += or
3993 *= etc which we need to handle by replacing the operator that is on the stack
3994 with a calculated assignment equivalent */
3995 if (!lastwasnumber && opstackhead) {
3996 int i = 0;
3997 while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
3998 i++;
4000 if (calcassignments[i].op == ' ') {
4001 rc = WCMD_NOOPERAND;
4002 goto exprerrorreturn;
4003 } else {
4004 /* Remove the operator on the stack, it will be replaced with a ?= equivalent
4005 when the general operator handling happens further down. */
4006 *pos = calcassignments[i].calculatedop;
4007 WCMD_popoperator(&opstackhead);
4011 /* Drop though */
4013 /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
4014 so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
4015 case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
4016 /* drop through */
4017 case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
4018 /* drop through */
4020 /* Normal operators - push onto stack unless precedence means we have to calculate it now */
4021 case '!': /* drop through */
4022 case '~': /* drop through */
4023 case '/': /* drop through */
4024 case '%': /* drop through */
4025 case '&': /* drop through */
4026 case '^': /* drop through */
4027 case '*': /* drop through */
4028 case '|':
4029 /* General code for handling most of the operators - look at the
4030 precedence of the top item on the stack, and see if we need to
4031 action the stack before we push something else onto it. */
4033 int precedence = WCMD_getprecedence(*pos);
4034 WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
4035 precedence, !opstackhead?-1:opstackhead->precedence);
4037 /* In general, for things with the same precedence, reduce immediately
4038 except for assignments and unary operators which do not */
4039 while (!rc && opstackhead &&
4040 ((opstackhead->precedence > precedence) ||
4041 ((opstackhead->precedence == precedence) &&
4042 (precedence != 1) && (precedence != 8)))) {
4043 rc = WCMD_reduce(&opstackhead, &varstackhead);
4045 if (rc) goto exprerrorreturn;
4046 WCMD_pushoperator(*pos, precedence, &opstackhead);
4047 pos++;
4048 break;
4051 /* comma means start a new expression, ie calculate what we have */
4052 case ',':
4054 int prevresult = -1;
4055 WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
4056 while (!rc && opstackhead) {
4057 rc = WCMD_reduce(&opstackhead, &varstackhead);
4059 if (rc) goto exprerrorreturn;
4060 /* If we have anything other than one number left, error
4061 otherwise throw the number away */
4062 if (!varstackhead || varstackhead->next) {
4063 rc = WCMD_NOOPERATOR;
4064 goto exprerrorreturn;
4066 prevresult = WCMD_popnumber(&varstackhead);
4067 WINE_TRACE("Expression resolved to %d\n", prevresult);
4068 free(varstackhead);
4069 varstackhead = NULL;
4070 pos++;
4071 break;
4074 /* Open bracket - use iteration to parse the inner expression, then continue */
4075 case '(' : {
4076 int exprresult = 0;
4077 pos++;
4078 rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
4079 if (rc) goto exprerrorreturn;
4080 WCMD_pushnumber(NULL, exprresult, &varstackhead);
4081 break;
4084 /* Close bracket - we have finished this depth, calculate and return */
4085 case ')' : {
4086 pos++;
4087 treatasnumber = TRUE; /* Things in brackets result in a number */
4088 if (depth == 0) {
4089 rc = WCMD_BADPAREN;
4090 goto exprerrorreturn;
4092 goto exprreturn;
4095 default:
4096 WINE_ERR("Unrecognized operator %c\n", *pos);
4097 pos++;
4099 lastwasnumber = treatasnumber;
4102 exprreturn:
4103 *expr = pos;
4105 /* We need to reduce until we have a single number (or variable) on the
4106 stack and set the return value to that */
4107 while (!rc && opstackhead) {
4108 rc = WCMD_reduce(&opstackhead, &varstackhead);
4110 if (rc) goto exprerrorreturn;
4112 /* If we have anything other than one number left, error
4113 otherwise throw the number away */
4114 if (!varstackhead || varstackhead->next) {
4115 rc = WCMD_NOOPERATOR;
4116 goto exprerrorreturn;
4119 /* Now get the number (and convert if it's just a variable name) */
4120 *ret = WCMD_popnumber(&varstackhead);
4122 exprerrorreturn:
4123 /* Free all remaining memory */
4124 while (opstackhead) WCMD_popoperator(&opstackhead);
4125 while (varstackhead) WCMD_popnumber(&varstackhead);
4127 WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
4128 return rc;
4131 /****************************************************************************
4132 * WCMD_setshow_env
4134 * Set/Show the environment variables
4137 void WCMD_setshow_env (WCHAR *s) {
4139 LPVOID env;
4140 WCHAR *p;
4141 BOOL status;
4142 WCHAR string[MAXSTRING];
4144 if (param1[0] == 0x00 && quals[0] == 0x00) {
4145 env = GetEnvironmentStringsW();
4146 WCMD_setshow_sortenv( env, NULL );
4147 return;
4150 /* See if /P supplied, and if so echo the prompt, and read in a reply */
4151 if (CompareStringW(LOCALE_USER_DEFAULT,
4152 NORM_IGNORECASE | SORT_STRINGSORT,
4153 s, 2, L"/P", -1) == CSTR_EQUAL) {
4154 DWORD count;
4156 s += 2;
4157 while (*s && (*s==' ' || *s=='\t')) s++;
4158 /* set /P "var=value"jim ignores anything after the last quote */
4159 if (*s=='\"') {
4160 WCHAR *lastquote;
4161 lastquote = WCMD_strip_quotes(s);
4162 if (lastquote) *lastquote = 0x00;
4163 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4166 /* If no parameter, or no '=' sign, return an error */
4167 if (!(*s) || ((p = wcschr (s, '=')) == NULL )) {
4168 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4169 return;
4172 /* Output the prompt */
4173 *p++ = '\0';
4174 if (*p) WCMD_output_asis(p);
4176 /* Read the reply */
4177 if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count) && count > 1) {
4178 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4179 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4180 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4181 wine_dbgstr_w(string));
4182 SetEnvironmentVariableW(s, string);
4185 /* See if /A supplied, and if so calculate the results of all the expressions */
4186 } else if (CompareStringW(LOCALE_USER_DEFAULT,
4187 NORM_IGNORECASE | SORT_STRINGSORT,
4188 s, 2, L"/A", -1) == CSTR_EQUAL) {
4189 /* /A supplied, so evaluate expressions and set variables appropriately */
4190 /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
4191 /* of the final computation */
4192 int result = 0;
4193 int rc = 0;
4194 WCHAR *thisexpr;
4195 WCHAR *src,*dst;
4197 /* Remove all quotes before doing any calculations */
4198 thisexpr = xalloc((wcslen(s + 2) + 1) * sizeof(WCHAR));
4199 src = s+2;
4200 dst = thisexpr;
4201 while (*src) {
4202 if (*src != '"') *dst++ = *src;
4203 src++;
4205 *dst = 0;
4207 /* Now calculate the results of the expression */
4208 src = thisexpr;
4209 rc = WCMD_handleExpression(&src, &result, 0);
4210 free(thisexpr);
4212 /* If parsing failed, issue the error message */
4213 if (rc > 0) {
4214 WCMD_output_stderr(WCMD_LoadMessage(rc));
4215 return;
4218 /* If we have no context (interactive or cmd.exe /c) print the final result */
4219 if (!context) {
4220 swprintf(string, ARRAY_SIZE(string), L"%d", result);
4221 WCMD_output_asis(string);
4224 } else {
4225 DWORD gle;
4227 /* set "var=value"jim ignores anything after the last quote */
4228 if (*s=='\"') {
4229 WCHAR *lastquote;
4230 lastquote = WCMD_strip_quotes(s);
4231 if (lastquote) *lastquote = 0x00;
4232 WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
4235 p = wcschr (s, '=');
4236 if (p == NULL) {
4237 env = GetEnvironmentStringsW();
4238 if (WCMD_setshow_sortenv( env, s ) == 0) {
4239 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
4240 errorlevel = 1;
4242 return;
4244 *p++ = '\0';
4246 if (!*p) p = NULL;
4247 WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
4248 wine_dbgstr_w(p));
4249 status = SetEnvironmentVariableW(s, p);
4250 gle = GetLastError();
4251 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
4252 errorlevel = 1;
4253 } else if (!status) WCMD_print_error();
4254 else if (!interactive) errorlevel = 0;
4258 /****************************************************************************
4259 * WCMD_setshow_path
4261 * Set/Show the path environment variable
4264 void WCMD_setshow_path (const WCHAR *args) {
4266 WCHAR string[1024];
4267 DWORD status;
4269 if (!*param1 && !*param2) {
4270 status = GetEnvironmentVariableW(L"PATH", string, ARRAY_SIZE(string));
4271 if (status != 0) {
4272 WCMD_output_asis(L"PATH=");
4273 WCMD_output_asis ( string);
4274 WCMD_output_asis(L"\r\n");
4276 else {
4277 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
4280 else {
4281 if (*args == '=') args++; /* Skip leading '=' */
4282 status = SetEnvironmentVariableW(L"PATH", args);
4283 if (!status) WCMD_print_error();
4287 /****************************************************************************
4288 * WCMD_setshow_prompt
4290 * Set or show the command prompt.
4293 void WCMD_setshow_prompt (void) {
4295 WCHAR *s;
4297 if (!*param1) {
4298 SetEnvironmentVariableW(L"PROMPT", NULL);
4300 else {
4301 s = param1;
4302 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
4303 if (!*s) {
4304 SetEnvironmentVariableW(L"PROMPT", NULL);
4306 else SetEnvironmentVariableW(L"PROMPT", s);
4310 /****************************************************************************
4311 * WCMD_setshow_time
4313 * Set/Show the system time
4314 * FIXME: Can't change time yet
4317 void WCMD_setshow_time (void) {
4319 WCHAR curtime[64], buffer[64];
4320 DWORD count;
4321 SYSTEMTIME st;
4323 if (!*param1) {
4324 GetLocalTime(&st);
4325 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, curtime, ARRAY_SIZE(curtime))) {
4326 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
4327 if (wcsstr(quals, L"/T") == NULL) {
4328 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
4329 if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count) &&
4330 count > 2) {
4331 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4335 else WCMD_print_error ();
4337 else {
4338 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
4342 /****************************************************************************
4343 * WCMD_shift
4345 * Shift batch parameters.
4346 * Optional /n says where to start shifting (n=0-8)
4349 void WCMD_shift (const WCHAR *args) {
4350 int start;
4352 if (context != NULL) {
4353 WCHAR *pos = wcschr(args, '/');
4354 int i;
4356 if (pos == NULL) {
4357 start = 0;
4358 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
4359 start = (*(pos+1) - '0');
4360 } else {
4361 SetLastError(ERROR_INVALID_PARAMETER);
4362 WCMD_print_error();
4363 return;
4366 WINE_TRACE("Shifting variables, starting at %d\n", start);
4367 for (i=start;i<=8;i++) {
4368 context -> shift_count[i] = context -> shift_count[i+1] + 1;
4370 context -> shift_count[9] = context -> shift_count[9] + 1;
4375 /****************************************************************************
4376 * WCMD_start
4378 void WCMD_start(WCHAR *args)
4380 int argno;
4381 int have_title;
4382 WCHAR file[MAX_PATH];
4383 WCHAR *cmdline, *cmdline_params;
4384 STARTUPINFOW st;
4385 PROCESS_INFORMATION pi;
4387 GetSystemDirectoryW( file, MAX_PATH );
4388 lstrcatW(file, L"\\start.exe");
4389 cmdline = xalloc( (wcslen(file) + wcslen(args) + 8) * sizeof(WCHAR) );
4390 lstrcpyW( cmdline, file );
4391 lstrcatW(cmdline, L" ");
4392 cmdline_params = cmdline + lstrlenW(cmdline);
4394 /* The start built-in has some special command-line parsing properties
4395 * which will be outlined here.
4397 * both '\t' and ' ' are argument separators
4398 * '/' has a special double role as both separator and switch prefix, e.g.
4400 * > start /low/i
4401 * or
4402 * > start "title"/i
4404 * are valid ways to pass multiple options to start. In the latter case
4405 * '/i' is not a part of the title but parsed as a switch.
4407 * However, '=', ';' and ',' are not separators:
4408 * > start "deus"=ex,machina
4410 * will in fact open a console titled 'deus=ex,machina'
4412 * The title argument parsing code is only interested in quotes themselves,
4413 * it does not respect escaping of any kind and all quotes are dropped
4414 * from the resulting title, therefore:
4416 * > start "\"" hello"/low
4418 * actually opens a console titled '\ hello' with low priorities.
4420 * To not break compatibility with wine programs relying on
4421 * wine's separate 'start.exe', this program's peculiar console
4422 * title parsing is actually implemented in 'cmd.exe' which is the
4423 * application native Windows programs will use to invoke 'start'.
4425 * WCMD_parameter_with_delims will take care of everything for us.
4427 have_title = FALSE;
4428 for (argno=0; ; argno++) {
4429 WCHAR *thisArg, *argN;
4431 argN = NULL;
4432 thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, L" \t/");
4434 /* No more parameters */
4435 if (!argN)
4436 break;
4438 /* Found the title */
4439 if (argN[0] == '"') {
4440 TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
4441 have_title = TRUE;
4443 /* Copy all of the cmdline processed */
4444 memcpy(cmdline_params, args, sizeof(WCHAR) * (argN - args));
4445 cmdline_params[argN - args] = '\0';
4447 /* Add quoted title */
4448 lstrcatW(cmdline_params, L"\"\\\"");
4449 lstrcatW(cmdline_params, thisArg);
4450 lstrcatW(cmdline_params, L"\\\"\"");
4452 /* Concatenate remaining command-line */
4453 thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, L" \t/");
4454 lstrcatW(cmdline_params, argN + lstrlenW(thisArg));
4456 break;
4459 /* Skipping a regular argument? */
4460 else if (argN != args && argN[-1] == '/') {
4461 continue;
4463 /* Not an argument nor the title, start of program arguments,
4464 * stop looking for title.
4466 } else
4467 break;
4470 /* build command-line if not built yet */
4471 if (!have_title) {
4472 lstrcatW( cmdline, args );
4475 memset( &st, 0, sizeof(STARTUPINFOW) );
4476 st.cb = sizeof(STARTUPINFOW);
4478 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
4480 WaitForSingleObject( pi.hProcess, INFINITE );
4481 GetExitCodeProcess( pi.hProcess, &errorlevel );
4482 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
4483 CloseHandle(pi.hProcess);
4484 CloseHandle(pi.hThread);
4486 else
4488 SetLastError(ERROR_FILE_NOT_FOUND);
4489 WCMD_print_error ();
4490 errorlevel = 9009;
4492 free(cmdline);
4495 /****************************************************************************
4496 * WCMD_title
4498 * Set the console title
4500 void WCMD_title (const WCHAR *args) {
4501 SetConsoleTitleW(args);
4504 /****************************************************************************
4505 * WCMD_type
4507 * Copy a file to standard output.
4510 void WCMD_type (WCHAR *args) {
4512 int argno = 0;
4513 WCHAR *argN = args;
4514 BOOL writeHeaders = FALSE;
4516 if (param1[0] == 0x00) {
4517 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
4518 return;
4521 if (param2[0] != 0x00) writeHeaders = TRUE;
4523 /* Loop through all args */
4524 errorlevel = 0;
4525 while (argN) {
4526 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4528 HANDLE h;
4529 WCHAR buffer[512];
4530 DWORD count;
4532 if (!argN) break;
4534 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4535 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4536 FILE_ATTRIBUTE_NORMAL, NULL);
4537 if (h == INVALID_HANDLE_VALUE) {
4538 WCMD_print_error ();
4539 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4540 errorlevel = 1;
4541 } else {
4542 if (writeHeaders) {
4543 WCMD_output_stderr(L"\n%1\n\n\n", thisArg);
4545 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer) - 1, &count)) {
4546 if (count == 0) break; /* ReadFile reports success on EOF! */
4547 buffer[count] = 0;
4548 WCMD_output_asis (buffer);
4550 CloseHandle (h);
4555 /****************************************************************************
4556 * WCMD_more
4558 * Output either a file or stdin to screen in pages
4561 void WCMD_more (WCHAR *args) {
4563 int argno = 0;
4564 WCHAR *argN = args;
4565 WCHAR moreStr[100];
4566 WCHAR moreStrPage[100];
4567 WCHAR buffer[512];
4568 DWORD count;
4570 /* Prefix the NLS more with '-- ', then load the text */
4571 errorlevel = 0;
4572 lstrcpyW(moreStr, L"-- ");
4573 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3], ARRAY_SIZE(moreStr)-3);
4575 if (param1[0] == 0x00) {
4577 /* Wine implements pipes via temporary files, and hence stdin is
4578 effectively reading from the file. This means the prompts for
4579 more are satisfied by the next line from the input (file). To
4580 avoid this, ensure stdin is to the console */
4581 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
4582 HANDLE hConIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
4583 FILE_SHARE_READ, NULL, OPEN_EXISTING,
4584 FILE_ATTRIBUTE_NORMAL, 0);
4585 WINE_TRACE("No parms - working probably in pipe mode\n");
4586 SetStdHandle(STD_INPUT_HANDLE, hConIn);
4588 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
4589 once you get in this bit unless due to a pipe, it's going to end badly... */
4590 wsprintfW(moreStrPage, L"%s --\n", moreStr);
4592 WCMD_enter_paged_mode(moreStrPage);
4593 while (WCMD_ReadFile(hstdin, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4594 if (count == 0) break; /* ReadFile reports success on EOF! */
4595 buffer[count] = 0;
4596 WCMD_output_asis (buffer);
4598 WCMD_leave_paged_mode();
4600 /* Restore stdin to what it was */
4601 SetStdHandle(STD_INPUT_HANDLE, hstdin);
4602 CloseHandle(hConIn);
4604 return;
4605 } else {
4606 BOOL needsPause = FALSE;
4608 /* Loop through all args */
4609 WINE_TRACE("Parms supplied - working through each file\n");
4610 WCMD_enter_paged_mode(moreStrPage);
4612 while (argN) {
4613 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
4614 HANDLE h;
4616 if (!argN) break;
4618 if (needsPause) {
4620 /* Wait */
4621 wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, 100);
4622 WCMD_leave_paged_mode();
4623 WCMD_output_asis(moreStrPage);
4624 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
4625 WCMD_enter_paged_mode(moreStrPage);
4629 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
4630 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
4631 FILE_ATTRIBUTE_NORMAL, NULL);
4632 if (h == INVALID_HANDLE_VALUE) {
4633 WCMD_print_error ();
4634 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
4635 errorlevel = 1;
4636 } else {
4637 ULONG64 curPos = 0;
4638 ULONG64 fileLen = 0;
4639 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
4641 /* Get the file size */
4642 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
4643 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
4645 needsPause = TRUE;
4646 while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer)-1, &count)) {
4647 if (count == 0) break; /* ReadFile reports success on EOF! */
4648 buffer[count] = 0;
4649 curPos += count;
4651 /* Update % count (would be used in WCMD_output_asis as prompt) */
4652 wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, (int) min(99, (curPos * 100)/fileLen));
4654 WCMD_output_asis (buffer);
4656 CloseHandle (h);
4660 WCMD_leave_paged_mode();
4664 /****************************************************************************
4665 * WCMD_verify
4667 * Display verify flag.
4668 * FIXME: We don't actually do anything with the verify flag other than toggle
4669 * it...
4672 void WCMD_verify (const WCHAR *args) {
4674 int count;
4676 count = lstrlenW(args);
4677 if (count == 0) {
4678 if (verify_mode) WCMD_output(WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"ON");
4679 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"OFF");
4680 return;
4682 if (lstrcmpiW(args, L"ON") == 0) {
4683 verify_mode = TRUE;
4684 return;
4686 else if (lstrcmpiW(args, L"OFF") == 0) {
4687 verify_mode = FALSE;
4688 return;
4690 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
4693 /****************************************************************************
4694 * WCMD_version
4696 * Display version info.
4699 void WCMD_version (void) {
4701 WCMD_output_asis (version_string);
4705 /****************************************************************************
4706 * WCMD_volume
4708 * Display volume information (set_label = FALSE)
4709 * Additionally set volume label (set_label = TRUE)
4710 * Returns 1 on success, 0 otherwise
4713 int WCMD_volume(BOOL set_label, const WCHAR *path)
4715 DWORD count, serial;
4716 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
4717 BOOL status;
4719 if (!*path) {
4720 status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
4721 if (!status) {
4722 WCMD_print_error ();
4723 return 0;
4725 status = GetVolumeInformationW(NULL, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4727 else {
4728 if ((path[1] != ':') || (lstrlenW(path) != 2)) {
4729 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
4730 return 0;
4732 wsprintfW (curdir, L"%s\\", path);
4733 status = GetVolumeInformationW(curdir, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
4735 if (!status) {
4736 WCMD_print_error ();
4737 return 0;
4739 if (label[0] != '\0') {
4740 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
4741 curdir[0], label);
4743 else {
4744 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
4745 curdir[0]);
4747 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
4748 HIWORD(serial), LOWORD(serial));
4749 if (set_label) {
4750 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
4751 if (WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count) &&
4752 count > 1) {
4753 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
4754 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
4756 if (*path) {
4757 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
4759 else {
4760 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
4763 return 1;
4766 /**************************************************************************
4767 * WCMD_exit
4769 * Exit either the process, or just this batch program
4773 void WCMD_exit (CMD_LIST **cmdList) {
4774 int rc = wcstol(param1, NULL, 10); /* Note: wcstol of empty parameter is 0 */
4776 if (context && lstrcmpiW(quals, L"/B") == 0) {
4777 errorlevel = rc;
4778 context -> skip_rest = TRUE;
4779 *cmdList = NULL;
4780 } else {
4781 ExitProcess(rc);
4786 /*****************************************************************************
4787 * WCMD_assoc
4789 * Lists or sets file associations (assoc = TRUE)
4790 * Lists or sets file types (assoc = FALSE)
4792 void WCMD_assoc (const WCHAR *args, BOOL assoc) {
4794 HKEY key;
4795 DWORD accessOptions = KEY_READ;
4796 WCHAR *newValue;
4797 LONG rc = ERROR_SUCCESS;
4798 WCHAR keyValue[MAXSTRING];
4799 DWORD valueLen;
4800 HKEY readKey;
4802 /* See if parameter includes '=' */
4803 errorlevel = 0;
4804 newValue = wcschr(args, '=');
4805 if (newValue) accessOptions |= KEY_WRITE;
4807 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
4808 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, L"", 0, accessOptions, &key) != ERROR_SUCCESS) {
4809 WINE_FIXME("Unexpected failure opening HKCR key: %ld\n", GetLastError());
4810 return;
4813 /* If no parameters then list all associations */
4814 if (*args == 0x00) {
4815 int index = 0;
4817 /* Enumerate all the keys */
4818 while (rc != ERROR_NO_MORE_ITEMS) {
4819 WCHAR keyName[MAXSTRING];
4820 DWORD nameLen;
4822 /* Find the next value */
4823 nameLen = MAXSTRING;
4824 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
4826 if (rc == ERROR_SUCCESS) {
4828 /* Only interested in extension ones if assoc, or others
4829 if not assoc */
4830 if ((keyName[0] == '.' && assoc) ||
4831 (!(keyName[0] == '.') && (!assoc)))
4833 WCHAR subkey[MAXSTRING];
4834 lstrcpyW(subkey, keyName);
4835 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4837 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4839 valueLen = sizeof(keyValue);
4840 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4841 WCMD_output_asis(keyName);
4842 WCMD_output_asis(L"=");
4843 /* If no default value found, leave line empty after '=' */
4844 if (rc == ERROR_SUCCESS) {
4845 WCMD_output_asis(keyValue);
4847 WCMD_output_asis(L"\r\n");
4848 RegCloseKey(readKey);
4854 } else {
4856 /* Parameter supplied - if no '=' on command line, it's a query */
4857 if (newValue == NULL) {
4858 WCHAR *space;
4859 WCHAR subkey[MAXSTRING];
4861 /* Query terminates the parameter at the first space */
4862 lstrcpyW(keyValue, args);
4863 space = wcschr(keyValue, ' ');
4864 if (space) *space=0x00;
4866 /* Set up key name */
4867 lstrcpyW(subkey, keyValue);
4868 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4870 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
4872 valueLen = sizeof(keyValue);
4873 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
4874 WCMD_output_asis(args);
4875 WCMD_output_asis(L"=");
4876 /* If no default value found, leave line empty after '=' */
4877 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
4878 WCMD_output_asis(L"\r\n");
4879 RegCloseKey(readKey);
4881 } else {
4882 WCHAR msgbuffer[MAXSTRING];
4884 /* Load the translated 'File association not found' */
4885 if (assoc) {
4886 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4887 } else {
4888 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4890 WCMD_output_stderr(msgbuffer, keyValue);
4891 errorlevel = 2;
4894 /* Not a query - it's a set or clear of a value */
4895 } else {
4897 WCHAR subkey[MAXSTRING];
4899 /* Get pointer to new value */
4900 *newValue = 0x00;
4901 newValue++;
4903 /* Set up key name */
4904 lstrcpyW(subkey, args);
4905 if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
4907 /* If nothing after '=' then clear value - only valid for ASSOC */
4908 if (*newValue == 0x00) {
4910 if (assoc) rc = RegDeleteKeyW(key, args);
4911 if (assoc && rc == ERROR_SUCCESS) {
4912 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
4914 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
4915 WCMD_print_error();
4916 errorlevel = 2;
4918 } else {
4919 WCHAR msgbuffer[MAXSTRING];
4921 /* Load the translated 'File association not found' */
4922 if (assoc) {
4923 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
4924 } else {
4925 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
4927 WCMD_output_stderr(msgbuffer, args);
4928 errorlevel = 2;
4931 /* It really is a set value = contents */
4932 } else {
4933 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
4934 accessOptions, NULL, &readKey, NULL);
4935 if (rc == ERROR_SUCCESS) {
4936 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
4937 (LPBYTE)newValue,
4938 sizeof(WCHAR) * (lstrlenW(newValue) + 1));
4939 RegCloseKey(readKey);
4942 if (rc != ERROR_SUCCESS) {
4943 WCMD_print_error();
4944 errorlevel = 2;
4945 } else {
4946 WCMD_output_asis(args);
4947 WCMD_output_asis(L"=");
4948 WCMD_output_asis(newValue);
4949 WCMD_output_asis(L"\r\n");
4955 /* Clean up */
4956 RegCloseKey(key);
4959 /****************************************************************************
4960 * WCMD_color
4962 * Colors the terminal screen.
4965 void WCMD_color (void) {
4967 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
4968 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
4970 if (param1[0] != 0x00 && lstrlenW(param1) > 2) {
4971 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
4972 return;
4975 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
4977 COORD topLeft;
4978 DWORD screenSize;
4979 DWORD color = 0;
4981 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
4983 topLeft.X = 0;
4984 topLeft.Y = 0;
4986 /* Convert the color hex digits */
4987 if (param1[0] == 0x00) {
4988 color = defaultColor;
4989 } else {
4990 color = wcstoul(param1, NULL, 16);
4993 /* Fail if fg == bg color */
4994 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
4995 errorlevel = 1;
4996 return;
4999 /* Set the current screen contents and ensure all future writes
5000 remain this color */
5001 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
5002 SetConsoleTextAttribute(hStdOut, color);
5006 /****************************************************************************
5007 * WCMD_mklink
5010 void WCMD_mklink(WCHAR *args)
5012 int argno = 0;
5013 WCHAR *argN = args;
5014 BOOL isdir = FALSE;
5015 BOOL junction = FALSE;
5016 BOOL hard = FALSE;
5017 BOOL ret = FALSE;
5018 WCHAR file1[MAX_PATH];
5019 WCHAR file2[MAX_PATH];
5021 if (param1[0] == 0x00 || param2[0] == 0x00) {
5022 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
5023 return;
5026 file1[0] = 0;
5028 while (argN) {
5029 WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
5031 if (!argN) break;
5033 WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
5035 if (lstrcmpiW(thisArg, L"/D") == 0)
5036 isdir = TRUE;
5037 else if (lstrcmpiW(thisArg, L"/H") == 0)
5038 hard = TRUE;
5039 else if (lstrcmpiW(thisArg, L"/J") == 0)
5040 junction = TRUE;
5041 else {
5042 if(!file1[0])
5043 lstrcpyW(file1, thisArg);
5044 else
5045 lstrcpyW(file2, thisArg);
5049 if(hard)
5050 ret = CreateHardLinkW(file1, file2, NULL);
5051 else if(!junction)
5052 ret = CreateSymbolicLinkW(file1, file2, isdir);
5053 else
5054 WINE_TRACE("Juction links currently not supported.\n");
5056 if(!ret)
5057 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), file1);