cmd: Remove an unused variable.
[wine/multimedia.git] / programs / cmd / builtins.c
blob272bac0a6548d8531331d89f93ba205661551933
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;
40 struct env_stack *pushd_directories;
41 const WCHAR dotW[] = {'.','\0'};
42 const WCHAR dotdotW[] = {'.','.','\0'};
43 const WCHAR nullW[] = {'\0'};
44 const WCHAR starW[] = {'*','\0'};
45 const WCHAR slashW[] = {'\\','\0'};
46 const WCHAR equalW[] = {'=','\0'};
47 const WCHAR inbuilt[][10] = {
48 {'C','A','L','L','\0'},
49 {'C','D','\0'},
50 {'C','H','D','I','R','\0'},
51 {'C','L','S','\0'},
52 {'C','O','P','Y','\0'},
53 {'C','T','T','Y','\0'},
54 {'D','A','T','E','\0'},
55 {'D','E','L','\0'},
56 {'D','I','R','\0'},
57 {'E','C','H','O','\0'},
58 {'E','R','A','S','E','\0'},
59 {'F','O','R','\0'},
60 {'G','O','T','O','\0'},
61 {'H','E','L','P','\0'},
62 {'I','F','\0'},
63 {'L','A','B','E','L','\0'},
64 {'M','D','\0'},
65 {'M','K','D','I','R','\0'},
66 {'M','O','V','E','\0'},
67 {'P','A','T','H','\0'},
68 {'P','A','U','S','E','\0'},
69 {'P','R','O','M','P','T','\0'},
70 {'R','E','M','\0'},
71 {'R','E','N','\0'},
72 {'R','E','N','A','M','E','\0'},
73 {'R','D','\0'},
74 {'R','M','D','I','R','\0'},
75 {'S','E','T','\0'},
76 {'S','H','I','F','T','\0'},
77 {'T','I','M','E','\0'},
78 {'T','I','T','L','E','\0'},
79 {'T','Y','P','E','\0'},
80 {'V','E','R','I','F','Y','\0'},
81 {'V','E','R','\0'},
82 {'V','O','L','\0'},
83 {'E','N','D','L','O','C','A','L','\0'},
84 {'S','E','T','L','O','C','A','L','\0'},
85 {'P','U','S','H','D','\0'},
86 {'P','O','P','D','\0'},
87 {'A','S','S','O','C','\0'},
88 {'C','O','L','O','R','\0'},
89 {'F','T','Y','P','E','\0'},
90 {'M','O','R','E','\0'},
91 {'C','H','O','I','C','E','\0'},
92 {'E','X','I','T','\0'}
94 static const WCHAR externals[][10] = {
95 {'A','T','T','R','I','B','\0'},
96 {'X','C','O','P','Y','\0'}
98 static const WCHAR fslashW[] = {'/','\0'};
99 static const WCHAR onW[] = {'O','N','\0'};
100 static const WCHAR offW[] = {'O','F','F','\0'};
101 static const WCHAR parmY[] = {'/','Y','\0'};
102 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
104 static HINSTANCE hinst;
105 static struct env_stack *saved_environment;
106 static BOOL verify_mode = FALSE;
108 /**************************************************************************
109 * WCMD_ask_confirm
111 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
112 * answer.
114 * Returns True if Y (or A) answer is selected
115 * If optionAll contains a pointer, ALL is allowed, and if answered
116 * set to TRUE
119 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
120 const BOOL *optionAll) {
122 WCHAR msgbuffer[MAXSTRING];
123 WCHAR Ybuffer[MAXSTRING];
124 WCHAR Nbuffer[MAXSTRING];
125 WCHAR Abuffer[MAXSTRING];
126 WCHAR answer[MAX_PATH] = {'\0'};
127 DWORD count = 0;
129 /* Load the translated 'Are you sure', plus valid answers */
130 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
131 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
132 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
133 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
135 /* Loop waiting on a Y or N */
136 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
137 static const WCHAR startBkt[] = {' ','(','\0'};
138 static const WCHAR endBkt[] = {')','?','\0'};
140 WCMD_output_asis (message);
141 if (showSureText) {
142 WCMD_output_asis (msgbuffer);
144 WCMD_output_asis (startBkt);
145 WCMD_output_asis (Ybuffer);
146 WCMD_output_asis (fslashW);
147 WCMD_output_asis (Nbuffer);
148 if (optionAll) {
149 WCMD_output_asis (fslashW);
150 WCMD_output_asis (Abuffer);
152 WCMD_output_asis (endBkt);
153 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
154 answer[0] = toupperW(answer[0]);
157 /* Return the answer */
158 return ((answer[0] == Ybuffer[0]) ||
159 (optionAll && (answer[0] == Abuffer[0])));
162 /****************************************************************************
163 * WCMD_clear_screen
165 * Clear the terminal screen.
168 void WCMD_clear_screen (void) {
170 /* Emulate by filling the screen from the top left to bottom right with
171 spaces, then moving the cursor to the top left afterwards */
172 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
173 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
175 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
177 COORD topLeft;
178 DWORD screenSize;
180 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
182 topLeft.X = 0;
183 topLeft.Y = 0;
184 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
185 SetConsoleCursorPosition(hStdOut, topLeft);
189 /****************************************************************************
190 * WCMD_change_tty
192 * Change the default i/o device (ie redirect STDin/STDout).
195 void WCMD_change_tty (void) {
197 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
201 /****************************************************************************
202 * WCMD_choice
206 void WCMD_choice (const WCHAR * command) {
208 static const WCHAR bellW[] = {7,0};
209 static const WCHAR commaW[] = {',',0};
210 static const WCHAR bracket_open[] = {'[',0};
211 static const WCHAR bracket_close[] = {']','?',0};
212 WCHAR answer[16];
213 WCHAR buffer[16];
214 WCHAR *ptr = NULL;
215 WCHAR *opt_c = NULL;
216 WCHAR *my_command = NULL;
217 WCHAR opt_default = 0;
218 DWORD opt_timeout = 0;
219 DWORD count;
220 DWORD oldmode;
221 DWORD have_console;
222 BOOL opt_n = FALSE;
223 BOOL opt_s = FALSE;
225 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
226 errorlevel = 0;
228 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
229 if (!my_command)
230 return;
232 ptr = WCMD_skip_leading_spaces(my_command);
233 while (*ptr == '/') {
234 switch (toupperW(ptr[1])) {
235 case 'C':
236 ptr += 2;
237 /* the colon is optional */
238 if (*ptr == ':')
239 ptr++;
241 if (!*ptr || isspaceW(*ptr)) {
242 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
243 HeapFree(GetProcessHeap(), 0, my_command);
244 return;
247 /* remember the allowed keys (overwrite previous /C option) */
248 opt_c = ptr;
249 while (*ptr && (!isspaceW(*ptr)))
250 ptr++;
252 if (*ptr) {
253 /* terminate allowed chars */
254 *ptr = 0;
255 ptr = WCMD_skip_leading_spaces(&ptr[1]);
257 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
258 break;
260 case 'N':
261 opt_n = TRUE;
262 ptr = WCMD_skip_leading_spaces(&ptr[2]);
263 break;
265 case 'S':
266 opt_s = TRUE;
267 ptr = WCMD_skip_leading_spaces(&ptr[2]);
268 break;
270 case 'T':
271 ptr = &ptr[2];
272 /* the colon is optional */
273 if (*ptr == ':')
274 ptr++;
276 opt_default = *ptr++;
278 if (!opt_default || (*ptr != ',')) {
279 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
280 HeapFree(GetProcessHeap(), 0, my_command);
281 return;
283 ptr++;
285 count = 0;
286 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
287 count++;
288 ptr++;
291 answer[count] = 0;
292 opt_timeout = atoiW(answer);
294 ptr = WCMD_skip_leading_spaces(ptr);
295 break;
297 default:
298 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
299 HeapFree(GetProcessHeap(), 0, my_command);
300 return;
304 if (opt_timeout)
305 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
307 if (have_console)
308 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
310 /* use default keys, when needed: localized versions of "Y"es and "No" */
311 if (!opt_c) {
312 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
313 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
314 opt_c = buffer;
315 buffer[2] = 0;
318 /* print the question, when needed */
319 if (*ptr)
320 WCMD_output_asis(ptr);
322 if (!opt_s) {
323 struprW(opt_c);
324 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
327 if (!opt_n) {
328 /* print a list of all allowed answers inside brackets */
329 WCMD_output_asis(bracket_open);
330 ptr = opt_c;
331 answer[1] = 0;
332 while ((answer[0] = *ptr++)) {
333 WCMD_output_asis(answer);
334 if (*ptr)
335 WCMD_output_asis(commaW);
337 WCMD_output_asis(bracket_close);
340 while (TRUE) {
342 /* FIXME: Add support for option /T */
343 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
345 if (!opt_s)
346 answer[0] = toupperW(answer[0]);
348 ptr = strchrW(opt_c, answer[0]);
349 if (ptr) {
350 WCMD_output_asis(answer);
351 WCMD_output_asis(newline);
352 if (have_console)
353 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
355 errorlevel = (ptr - opt_c) + 1;
356 WINE_TRACE("answer: %d\n", errorlevel);
357 HeapFree(GetProcessHeap(), 0, my_command);
358 return;
360 else
362 /* key not allowed: play the bell */
363 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
364 WCMD_output_asis(bellW);
369 /****************************************************************************
370 * WCMD_copy
372 * Copy a file or wildcarded set.
373 * FIXME: Add support for a+b+c type syntax
376 void WCMD_copy (void) {
378 WIN32_FIND_DATAW fd;
379 HANDLE hff;
380 BOOL force, status;
381 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
382 DWORD len;
383 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
384 BOOL copyToDir = FALSE;
385 WCHAR srcspec[MAX_PATH];
386 DWORD attribs;
387 WCHAR drive[10];
388 WCHAR dir[MAX_PATH];
389 WCHAR fname[MAX_PATH];
390 WCHAR ext[MAX_PATH];
392 if (param1[0] == 0x00) {
393 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
394 return;
397 /* Convert source into full spec */
398 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
399 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
400 if (srcpath[strlenW(srcpath) - 1] == '\\')
401 srcpath[strlenW(srcpath) - 1] = '\0';
403 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
404 attribs = GetFileAttributesW(srcpath);
405 } else {
406 attribs = 0;
408 strcpyW(srcspec, srcpath);
410 /* If a directory, then add \* on the end when searching */
411 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
412 strcatW(srcpath, slashW);
413 strcatW(srcspec, slashW);
414 strcatW(srcspec, starW);
415 } else {
416 WCMD_splitpath(srcpath, drive, dir, fname, ext);
417 strcpyW(srcpath, drive);
418 strcatW(srcpath, dir);
421 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
423 /* If no destination supplied, assume current directory */
424 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
425 if (param2[0] == 0x00) {
426 strcpyW(param2, dotW);
429 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
430 if (outpath[strlenW(outpath) - 1] == '\\')
431 outpath[strlenW(outpath) - 1] = '\0';
432 attribs = GetFileAttributesW(outpath);
433 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
434 strcatW (outpath, slashW);
435 copyToDir = TRUE;
437 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
438 wine_dbgstr_w(outpath), copyToDir);
440 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
441 if (strstrW (quals, parmNoY))
442 force = FALSE;
443 else if (strstrW (quals, parmY))
444 force = TRUE;
445 else {
446 /* By default, we will force the overwrite in batch mode and ask for
447 * confirmation in interactive mode. */
448 force = !!context;
450 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
451 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
452 * default behavior. */
453 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
454 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
455 if (!lstrcmpiW (copycmd, parmY))
456 force = TRUE;
457 else if (!lstrcmpiW (copycmd, parmNoY))
458 force = FALSE;
462 /* Loop through all source files */
463 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
464 hff = FindFirstFileW(srcspec, &fd);
465 if (hff != INVALID_HANDLE_VALUE) {
466 do {
467 WCHAR outname[MAX_PATH];
468 WCHAR srcname[MAX_PATH];
469 BOOL overwrite = force;
471 /* Destination is either supplied filename, or source name in
472 supplied destination directory */
473 strcpyW(outname, outpath);
474 if (copyToDir) strcatW(outname, fd.cFileName);
475 strcpyW(srcname, srcpath);
476 strcatW(srcname, fd.cFileName);
478 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
479 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
481 /* Skip . and .., and directories */
482 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
483 overwrite = FALSE;
484 WINE_TRACE("Skipping directories\n");
487 /* Prompt before overwriting */
488 else if (!overwrite) {
489 attribs = GetFileAttributesW(outname);
490 if (attribs != INVALID_FILE_ATTRIBUTES) {
491 WCHAR* question;
492 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
493 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
494 LocalFree(question);
496 else overwrite = TRUE;
499 /* Do the copy as appropriate */
500 if (overwrite) {
501 status = CopyFileW(srcname, outname, FALSE);
502 if (!status) WCMD_print_error ();
505 } while (FindNextFileW(hff, &fd) != 0);
506 FindClose (hff);
507 } else {
508 WCMD_print_error ();
512 /****************************************************************************
513 * WCMD_create_dir
515 * Create a directory (and, if needed, any intermediate directories).
517 * Modifies its argument by replacing slashes temporarily with nulls.
520 static BOOL create_full_path(WCHAR* path)
522 WCHAR *p, *start;
524 /* don't mess with drive letter portion of path, if any */
525 start = path;
526 if (path[1] == ':')
527 start = path+2;
529 /* Strip trailing slashes. */
530 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
531 *p = 0;
533 /* Step through path, creating intermediate directories as needed. */
534 /* First component includes drive letter, if any. */
535 p = start;
536 for (;;) {
537 DWORD rv;
538 /* Skip to end of component */
539 while (*p == '\\') p++;
540 while (*p && *p != '\\') p++;
541 if (!*p) {
542 /* path is now the original full path */
543 return CreateDirectoryW(path, NULL);
545 /* Truncate path, create intermediate directory, and restore path */
546 *p = 0;
547 rv = CreateDirectoryW(path, NULL);
548 *p = '\\';
549 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
550 return FALSE;
552 /* notreached */
553 return FALSE;
556 void WCMD_create_dir (WCHAR *command) {
557 int argno = 0;
558 WCHAR *argN = command;
560 if (param1[0] == 0x00) {
561 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
562 return;
564 /* Loop through all args */
565 while (TRUE) {
566 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL);
567 if (!argN) break;
568 if (!create_full_path(thisArg)) {
569 WCMD_print_error ();
570 errorlevel = 1;
575 /* Parse the /A options given by the user on the commandline
576 * into a bitmask of wanted attributes (*wantSet),
577 * and a bitmask of unwanted attributes (*wantClear).
579 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
580 static const WCHAR parmA[] = {'/','A','\0'};
581 WCHAR *p;
583 /* both are strictly 'out' parameters */
584 *wantSet=0;
585 *wantClear=0;
587 /* For each /A argument */
588 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
589 /* Skip /A itself */
590 p += 2;
592 /* Skip optional : */
593 if (*p == ':') p++;
595 /* For each of the attribute specifier chars to this /A option */
596 for (; *p != 0 && *p != '/'; p++) {
597 BOOL negate = FALSE;
598 DWORD mask = 0;
600 if (*p == '-') {
601 negate=TRUE;
602 p++;
605 /* Convert the attribute specifier to a bit in one of the masks */
606 switch (*p) {
607 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
608 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
609 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
610 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
611 default:
612 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
614 if (negate)
615 *wantClear |= mask;
616 else
617 *wantSet |= mask;
622 /* If filename part of parameter is * or *.*,
623 * and neither /Q nor /P options were given,
624 * prompt the user whether to proceed.
625 * Returns FALSE if user says no, TRUE otherwise.
626 * *pPrompted is set to TRUE if the user is prompted.
627 * (If /P supplied, del will prompt for individual files later.)
629 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
630 static const WCHAR parmP[] = {'/','P','\0'};
631 static const WCHAR parmQ[] = {'/','Q','\0'};
633 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
634 static const WCHAR anyExt[]= {'.','*','\0'};
635 WCHAR drive[10];
636 WCHAR dir[MAX_PATH];
637 WCHAR fname[MAX_PATH];
638 WCHAR ext[MAX_PATH];
639 WCHAR fpath[MAX_PATH];
641 /* Convert path into actual directory spec */
642 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
643 WCMD_splitpath(fpath, drive, dir, fname, ext);
645 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
646 if ((strcmpW(fname, starW) == 0) &&
647 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
649 WCHAR question[MAXSTRING];
650 static const WCHAR fmt[] = {'%','s',' ','\0'};
652 /* Caller uses this to suppress "file not found" warning later */
653 *pPrompted = TRUE;
655 /* Ask for confirmation */
656 wsprintfW(question, fmt, fpath);
657 return WCMD_ask_confirm(question, TRUE, NULL);
660 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
661 return TRUE;
664 /* Helper function for WCMD_delete().
665 * Deletes a single file, directory, or wildcard.
666 * If /S was given, does it recursively.
667 * Returns TRUE if a file was deleted.
669 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
671 static const WCHAR parmP[] = {'/','P','\0'};
672 static const WCHAR parmS[] = {'/','S','\0'};
673 static const WCHAR parmF[] = {'/','F','\0'};
674 DWORD wanted_attrs;
675 DWORD unwanted_attrs;
676 BOOL found = FALSE;
677 WCHAR argCopy[MAX_PATH];
678 WIN32_FIND_DATAW fd;
679 HANDLE hff;
680 WCHAR fpath[MAX_PATH];
681 WCHAR *p;
682 BOOL handleParm = TRUE;
684 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
686 strcpyW(argCopy, thisArg);
687 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
688 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
690 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
691 /* Skip this arg if user declines to delete *.* */
692 return FALSE;
695 /* First, try to delete in the current directory */
696 hff = FindFirstFileW(argCopy, &fd);
697 if (hff == INVALID_HANDLE_VALUE) {
698 handleParm = FALSE;
699 } else {
700 found = TRUE;
703 /* Support del <dirname> by just deleting all files dirname\* */
704 if (handleParm
705 && (strchrW(argCopy,'*') == NULL)
706 && (strchrW(argCopy,'?') == NULL)
707 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
709 WCHAR modifiedParm[MAX_PATH];
710 static const WCHAR slashStar[] = {'\\','*','\0'};
712 strcpyW(modifiedParm, argCopy);
713 strcatW(modifiedParm, slashStar);
714 FindClose(hff);
715 found = TRUE;
716 WCMD_delete_one(modifiedParm);
718 } else if (handleParm) {
720 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
721 strcpyW (fpath, argCopy);
722 do {
723 p = strrchrW (fpath, '\\');
724 if (p != NULL) {
725 *++p = '\0';
726 strcatW (fpath, fd.cFileName);
728 else strcpyW (fpath, fd.cFileName);
729 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
730 BOOL ok;
732 /* Handle attribute matching (/A) */
733 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
734 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
736 /* /P means prompt for each file */
737 if (ok && strstrW (quals, parmP) != NULL) {
738 WCHAR* question;
740 /* Ask for confirmation */
741 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
742 ok = WCMD_ask_confirm(question, FALSE, NULL);
743 LocalFree(question);
746 /* Only proceed if ok to */
747 if (ok) {
749 /* If file is read only, and /A:r or /F supplied, delete it */
750 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
751 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
752 strstrW (quals, parmF) != NULL)) {
753 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
756 /* Now do the delete */
757 if (!DeleteFileW(fpath)) WCMD_print_error ();
761 } while (FindNextFileW(hff, &fd) != 0);
762 FindClose (hff);
765 /* Now recurse into all subdirectories handling the parameter in the same way */
766 if (strstrW (quals, parmS) != NULL) {
768 WCHAR thisDir[MAX_PATH];
769 int cPos;
771 WCHAR drive[10];
772 WCHAR dir[MAX_PATH];
773 WCHAR fname[MAX_PATH];
774 WCHAR ext[MAX_PATH];
776 /* Convert path into actual directory spec */
777 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
778 WCMD_splitpath(thisDir, drive, dir, fname, ext);
780 strcpyW(thisDir, drive);
781 strcatW(thisDir, dir);
782 cPos = strlenW(thisDir);
784 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
786 /* Append '*' to the directory */
787 thisDir[cPos] = '*';
788 thisDir[cPos+1] = 0x00;
790 hff = FindFirstFileW(thisDir, &fd);
792 /* Remove residual '*' */
793 thisDir[cPos] = 0x00;
795 if (hff != INVALID_HANDLE_VALUE) {
796 DIRECTORY_STACK *allDirs = NULL;
797 DIRECTORY_STACK *lastEntry = NULL;
799 do {
800 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
801 (strcmpW(fd.cFileName, dotdotW) != 0) &&
802 (strcmpW(fd.cFileName, dotW) != 0)) {
804 DIRECTORY_STACK *nextDir;
805 WCHAR subParm[MAX_PATH];
807 /* Work out search parameter in sub dir */
808 strcpyW (subParm, thisDir);
809 strcatW (subParm, fd.cFileName);
810 strcatW (subParm, slashW);
811 strcatW (subParm, fname);
812 strcatW (subParm, ext);
813 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
815 /* Allocate memory, add to list */
816 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
817 if (allDirs == NULL) allDirs = nextDir;
818 if (lastEntry != NULL) lastEntry->next = nextDir;
819 lastEntry = nextDir;
820 nextDir->next = NULL;
821 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
822 (strlenW(subParm)+1) * sizeof(WCHAR));
823 strcpyW(nextDir->dirName, subParm);
825 } while (FindNextFileW(hff, &fd) != 0);
826 FindClose (hff);
828 /* Go through each subdir doing the delete */
829 while (allDirs != NULL) {
830 DIRECTORY_STACK *tempDir;
832 tempDir = allDirs->next;
833 found |= WCMD_delete_one (allDirs->dirName);
835 HeapFree(GetProcessHeap(),0,allDirs->dirName);
836 HeapFree(GetProcessHeap(),0,allDirs);
837 allDirs = tempDir;
842 return found;
845 /****************************************************************************
846 * WCMD_delete
848 * Delete a file or wildcarded set.
850 * Note on /A:
851 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
852 * - Each set is a pattern, eg /ahr /as-r means
853 * readonly+hidden OR nonreadonly system files
854 * - The '-' applies to a single field, ie /a:-hr means read only
855 * non-hidden files
858 BOOL WCMD_delete (WCHAR *command) {
859 int argno;
860 WCHAR *argN;
861 BOOL argsProcessed = FALSE;
862 BOOL foundAny = FALSE;
864 errorlevel = 0;
866 for (argno=0; ; argno++) {
867 BOOL found;
868 WCHAR *thisArg;
870 argN = NULL;
871 thisArg = WCMD_parameter (command, argno, &argN, NULL);
872 if (!argN)
873 break; /* no more parameters */
874 if (argN[0] == '/')
875 continue; /* skip options */
877 argsProcessed = TRUE;
878 found = WCMD_delete_one(thisArg);
879 if (!found) {
880 errorlevel = 1;
881 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
883 foundAny |= found;
886 /* Handle no valid args */
887 if (!argsProcessed)
888 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
890 return foundAny;
894 * WCMD_strtrim
896 * Returns a trimmed version of s with all leading and trailing whitespace removed
897 * Pre: s non NULL
900 static WCHAR *WCMD_strtrim(const WCHAR *s)
902 DWORD len = strlenW(s);
903 const WCHAR *start = s;
904 WCHAR* result;
906 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
907 return NULL;
909 while (isspaceW(*start)) start++;
910 if (*start) {
911 const WCHAR *end = s + len - 1;
912 while (end > start && isspaceW(*end)) end--;
913 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
914 result[end - start + 1] = '\0';
915 } else {
916 result[0] = '\0';
919 return result;
922 /****************************************************************************
923 * WCMD_echo
925 * Echo input to the screen (or not). We don't try to emulate the bugs
926 * in DOS (try typing "ECHO ON AGAIN" for an example).
929 void WCMD_echo (const WCHAR *command)
931 int count;
932 const WCHAR *origcommand = command;
933 WCHAR *trimmed;
935 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
936 || command[0]==':' || command[0]==';')
937 command++;
939 trimmed = WCMD_strtrim(command);
940 if (!trimmed) return;
942 count = strlenW(trimmed);
943 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
944 && origcommand[0]!=';') {
945 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
946 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
947 return;
950 if (lstrcmpiW(trimmed, onW) == 0)
951 echo_mode = TRUE;
952 else if (lstrcmpiW(trimmed, offW) == 0)
953 echo_mode = FALSE;
954 else {
955 WCMD_output_asis (command);
956 WCMD_output_asis (newline);
958 HeapFree(GetProcessHeap(), 0, trimmed);
961 /*****************************************************************************
962 * WCMD_part_execute
964 * Execute a command, and any && or bracketed follow on to the command. The
965 * first command to be executed may not be at the front of the
966 * commands->thiscommand string (eg. it may point after a DO or ELSE)
968 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
969 const WCHAR *variable, const WCHAR *value,
970 BOOL isIF, BOOL conditionTRUE)
972 CMD_LIST *curPosition = *cmdList;
973 int myDepth = (*cmdList)->bracketDepth;
975 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
976 cmdList, wine_dbgstr_w(firstcmd),
977 wine_dbgstr_w(variable), wine_dbgstr_w(value),
978 conditionTRUE);
980 /* Skip leading whitespace between condition and the command */
981 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
983 /* Process the first command, if there is one */
984 if (conditionTRUE && firstcmd && *firstcmd) {
985 WCHAR *command = WCMD_strdupW(firstcmd);
986 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
987 HeapFree(GetProcessHeap(), 0, command);
991 /* If it didn't move the position, step to next command */
992 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
994 /* Process any other parts of the command */
995 if (*cmdList) {
996 BOOL processThese = TRUE;
998 if (isIF) processThese = conditionTRUE;
1000 while (*cmdList) {
1001 static const WCHAR ifElse[] = {'e','l','s','e'};
1003 /* execute all appropriate commands */
1004 curPosition = *cmdList;
1006 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1007 *cmdList,
1008 (*cmdList)->prevDelim,
1009 (*cmdList)->bracketDepth, myDepth);
1011 /* Execute any statements appended to the line */
1012 /* FIXME: Only if previous call worked for && or failed for || */
1013 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1014 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1015 if (processThese && (*cmdList)->command) {
1016 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1017 value, cmdList);
1019 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1021 /* Execute any appended to the statement with (...) */
1022 } else if ((*cmdList)->bracketDepth > myDepth) {
1023 if (processThese) {
1024 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1025 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1027 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1029 /* End of the command - does 'ELSE ' follow as the next command? */
1030 } else {
1031 if (isIF
1032 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1033 (*cmdList)->command)) {
1035 /* Swap between if and else processing */
1036 processThese = !processThese;
1038 /* Process the ELSE part */
1039 if (processThese) {
1040 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1041 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1043 /* Skip leading whitespace between condition and the command */
1044 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1045 if (*cmd) {
1046 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1049 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1050 } else {
1051 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1052 break;
1057 return;
1060 /**************************************************************************
1061 * WCMD_for
1063 * Batch file loop processing.
1065 * On entry: cmdList contains the syntax up to the set
1066 * next cmdList and all in that bracket contain the set data
1067 * next cmdlist contains the DO cmd
1068 * following that is either brackets or && entries (as per if)
1072 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1074 WIN32_FIND_DATAW fd;
1075 HANDLE hff;
1076 int i;
1077 static const WCHAR inW[] = {'i','n'};
1078 static const WCHAR doW[] = {'d','o'};
1079 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1080 WCHAR variable[4];
1081 WCHAR *firstCmd;
1082 int thisDepth;
1084 WCHAR *curPos = p;
1085 BOOL expandDirs = FALSE;
1086 BOOL useNumbers = FALSE;
1087 BOOL doFileset = FALSE;
1088 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1089 int itemNum;
1090 CMD_LIST *thisCmdStart;
1093 /* Handle optional qualifiers (multiple are allowed) */
1094 while (*curPos && *curPos == '/') {
1095 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
1096 curPos++;
1097 switch (toupperW(*curPos)) {
1098 case 'D': curPos++; expandDirs = TRUE; break;
1099 case 'L': curPos++; useNumbers = TRUE; break;
1101 /* Recursive is special case - /R can have an optional path following it */
1102 /* filenamesets are another special case - /F can have an optional options following it */
1103 case 'R':
1104 case 'F':
1106 BOOL isRecursive = (*curPos == 'R');
1108 if (!isRecursive)
1109 doFileset = TRUE;
1111 /* Skip whitespace */
1112 curPos++;
1113 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1115 /* Next parm is either qualifier, path/options or variable -
1116 only care about it if it is the path/options */
1117 if (*curPos && *curPos != '/' && *curPos != '%') {
1118 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
1119 else {
1120 static unsigned int once;
1121 if (!once++) WINE_FIXME("/F needs to handle options\n");
1124 break;
1126 default:
1127 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
1128 curPos++;
1131 /* Skip whitespace between qualifiers */
1132 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1135 /* Skip whitespace before variable */
1136 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1138 /* Ensure line continues with variable */
1139 if (!*curPos || *curPos != '%') {
1140 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1141 return;
1144 /* Variable should follow */
1145 i = 0;
1146 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
1147 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
1148 variable[i] = 0x00;
1149 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1150 curPos = &curPos[i];
1152 /* Skip whitespace before IN */
1153 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1155 /* Ensure line continues with IN */
1156 if (!*curPos
1157 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
1159 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1160 return;
1163 /* Save away where the set of data starts and the variable */
1164 thisDepth = (*cmdList)->bracketDepth;
1165 *cmdList = (*cmdList)->nextcommand;
1166 setStart = (*cmdList);
1168 /* Skip until the close bracket */
1169 WINE_TRACE("Searching %p as the set\n", *cmdList);
1170 while (*cmdList &&
1171 (*cmdList)->command != NULL &&
1172 (*cmdList)->bracketDepth > thisDepth) {
1173 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1174 *cmdList = (*cmdList)->nextcommand;
1177 /* Skip the close bracket, if there is one */
1178 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1180 /* Syntax error if missing close bracket, or nothing following it
1181 and once we have the complete set, we expect a DO */
1182 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1183 if ((*cmdList == NULL)
1184 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1186 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1187 return;
1190 /* Save away the starting position for the commands (and offset for the
1191 first one */
1192 cmdStart = *cmdList;
1193 cmdEnd = *cmdList;
1194 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1195 itemNum = 0;
1197 thisSet = setStart;
1198 /* Loop through all set entries */
1199 while (thisSet &&
1200 thisSet->command != NULL &&
1201 thisSet->bracketDepth >= thisDepth) {
1203 /* Loop through all entries on the same line */
1204 WCHAR *item;
1205 WCHAR *itemStart;
1207 WINE_TRACE("Processing for set %p\n", thisSet);
1208 i = 0;
1209 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1212 * If the parameter within the set has a wildcard then search for matching files
1213 * otherwise do a literal substitution.
1215 static const WCHAR wildcards[] = {'*','?','\0'};
1216 thisCmdStart = cmdStart;
1218 itemNum++;
1219 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1221 if (!useNumbers && !doFileset) {
1222 if (strpbrkW (item, wildcards)) {
1223 hff = FindFirstFileW(item, &fd);
1224 if (hff != INVALID_HANDLE_VALUE) {
1225 do {
1226 BOOL isDirectory = FALSE;
1228 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1230 /* Handle as files or dirs appropriately, but ignore . and .. */
1231 if (isDirectory == expandDirs &&
1232 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1233 (strcmpW(fd.cFileName, dotW) != 0))
1235 thisCmdStart = cmdStart;
1236 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1237 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1238 fd.cFileName, FALSE, TRUE);
1241 } while (FindNextFileW(hff, &fd) != 0);
1242 FindClose (hff);
1244 } else {
1245 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1248 } else if (useNumbers) {
1249 /* Convert the first 3 numbers to signed longs and save */
1250 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1251 /* else ignore them! */
1253 /* Filesets - either a list of files, or a command to run and parse the output */
1254 } else if (doFileset && *itemStart != '"') {
1256 HANDLE input;
1257 WCHAR temp_file[MAX_PATH];
1259 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1260 wine_dbgstr_w(item));
1262 /* If backquote or single quote, we need to launch that command
1263 and parse the results - use a temporary file */
1264 if (*itemStart == '`' || *itemStart == '\'') {
1266 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1267 static const WCHAR redirOut[] = {'>','%','s','\0'};
1268 static const WCHAR cmdW[] = {'C','M','D','\0'};
1270 /* Remove trailing character */
1271 itemStart[strlenW(itemStart)-1] = 0x00;
1273 /* Get temp filename */
1274 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1275 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1277 /* Execute program and redirect output */
1278 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1279 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1281 /* Open the file, read line by line and process */
1282 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1283 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1284 } else {
1286 /* Open the file, read line by line and process */
1287 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1288 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1291 /* Process the input file */
1292 if (input == INVALID_HANDLE_VALUE) {
1293 WCMD_print_error ();
1294 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1295 errorlevel = 1;
1296 return; /* FOR loop aborts at first failure here */
1298 } else {
1300 WCHAR buffer[MAXSTRING] = {'\0'};
1301 WCHAR *where, *parm;
1303 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1305 /* Skip blank lines*/
1306 parm = WCMD_parameter (buffer, 0, &where, NULL);
1307 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1308 wine_dbgstr_w(buffer));
1310 if (where) {
1311 /* FIXME: The following should be moved into its own routine and
1312 reused for the string literal parsing below */
1313 thisCmdStart = cmdStart;
1314 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1315 cmdEnd = thisCmdStart;
1318 buffer[0] = 0x00;
1321 CloseHandle (input);
1324 /* Delete the temporary file */
1325 if (*itemStart == '`' || *itemStart == '\'') {
1326 DeleteFileW(temp_file);
1329 /* Filesets - A string literal */
1330 } else if (doFileset && *itemStart == '"') {
1331 WCHAR buffer[MAXSTRING] = {'\0'};
1332 WCHAR *where, *parm;
1334 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1335 strcpyW(buffer, item);
1336 parm = WCMD_parameter (buffer, 0, &where, NULL);
1337 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1338 wine_dbgstr_w(buffer));
1340 if (where) {
1341 /* FIXME: The following should be moved into its own routine and
1342 reused for the string literal parsing below */
1343 thisCmdStart = cmdStart;
1344 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1345 cmdEnd = thisCmdStart;
1349 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1350 cmdEnd = thisCmdStart;
1351 i++;
1354 /* Move onto the next set line */
1355 thisSet = thisSet->nextcommand;
1358 /* If /L is provided, now run the for loop */
1359 if (useNumbers) {
1360 WCHAR thisNum[20];
1361 static const WCHAR fmt[] = {'%','d','\0'};
1363 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1364 numbers[0], numbers[2], numbers[1]);
1365 for (i=numbers[0];
1366 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1367 i=i + numbers[1]) {
1369 sprintfW(thisNum, fmt, i);
1370 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1372 thisCmdStart = cmdStart;
1373 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1374 cmdEnd = thisCmdStart;
1378 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1379 all processing, OR it should be pointing to the end of && processing OR
1380 it should be pointing at the NULL end of bracket for the DO. The return
1381 value needs to be the NEXT command to execute, which it either is, or
1382 we need to step over the closing bracket */
1383 *cmdList = cmdEnd;
1384 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1387 /**************************************************************************
1388 * WCMD_give_help
1390 * Simple on-line help. Help text is stored in the resource file.
1393 void WCMD_give_help (const WCHAR *command)
1395 size_t i;
1397 command = WCMD_skip_leading_spaces((WCHAR*) command);
1398 if (strlenW(command) == 0) {
1399 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1401 else {
1402 /* Display help message for builtin commands */
1403 for (i=0; i<=WCMD_EXIT; i++) {
1404 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1405 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1406 WCMD_output_asis (WCMD_LoadMessage(i));
1407 return;
1410 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1411 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1412 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1413 command, -1, externals[i], -1) == CSTR_EQUAL) {
1414 WCHAR cmd[128];
1415 static const WCHAR helpW[] = {' ', '/','?','\0'};
1416 strcpyW(cmd, command);
1417 strcatW(cmd, helpW);
1418 WCMD_run_program(cmd, 0);
1419 return;
1422 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1424 return;
1427 /****************************************************************************
1428 * WCMD_go_to
1430 * Batch file jump instruction. Not the most efficient algorithm ;-)
1431 * Prints error message if the specified label cannot be found - the file pointer is
1432 * then at EOF, effectively stopping the batch file.
1433 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1436 void WCMD_goto (CMD_LIST **cmdList) {
1438 WCHAR string[MAX_PATH];
1439 WCHAR current[MAX_PATH];
1441 /* Do not process any more parts of a processed multipart or multilines command */
1442 if (cmdList) *cmdList = NULL;
1444 if (context != NULL) {
1445 WCHAR *paramStart = param1, *str;
1446 static const WCHAR eofW[] = {':','e','o','f','\0'};
1448 if (param1[0] == 0x00) {
1449 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1450 return;
1453 /* Handle special :EOF label */
1454 if (lstrcmpiW (eofW, param1) == 0) {
1455 context -> skip_rest = TRUE;
1456 return;
1459 /* Support goto :label as well as goto label */
1460 if (*paramStart == ':') paramStart++;
1462 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1463 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1464 str = string;
1465 while (isspaceW (*str)) str++;
1466 if (*str == ':') {
1467 DWORD index = 0;
1468 str++;
1469 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1470 index++;
1472 /* ignore space at the end */
1473 current[index] = 0;
1474 if (lstrcmpiW (current, paramStart) == 0) return;
1477 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1479 return;
1482 /*****************************************************************************
1483 * WCMD_pushd
1485 * Push a directory onto the stack
1488 void WCMD_pushd (const WCHAR *command)
1490 struct env_stack *curdir;
1491 WCHAR *thisdir;
1492 static const WCHAR parmD[] = {'/','D','\0'};
1494 if (strchrW(command, '/') != NULL) {
1495 SetLastError(ERROR_INVALID_PARAMETER);
1496 WCMD_print_error();
1497 return;
1500 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1501 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1502 if( !curdir || !thisdir ) {
1503 LocalFree(curdir);
1504 LocalFree(thisdir);
1505 WINE_ERR ("out of memory\n");
1506 return;
1509 /* Change directory using CD code with /D parameter */
1510 strcpyW(quals, parmD);
1511 GetCurrentDirectoryW (1024, thisdir);
1512 errorlevel = 0;
1513 WCMD_setshow_default(command);
1514 if (errorlevel) {
1515 LocalFree(curdir);
1516 LocalFree(thisdir);
1517 return;
1518 } else {
1519 curdir -> next = pushd_directories;
1520 curdir -> strings = thisdir;
1521 if (pushd_directories == NULL) {
1522 curdir -> u.stackdepth = 1;
1523 } else {
1524 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1526 pushd_directories = curdir;
1531 /*****************************************************************************
1532 * WCMD_popd
1534 * Pop a directory from the stack
1537 void WCMD_popd (void) {
1538 struct env_stack *temp = pushd_directories;
1540 if (!pushd_directories)
1541 return;
1543 /* pop the old environment from the stack, and make it the current dir */
1544 pushd_directories = temp->next;
1545 SetCurrentDirectoryW(temp->strings);
1546 LocalFree (temp->strings);
1547 LocalFree (temp);
1550 /****************************************************************************
1551 * WCMD_if
1553 * Batch file conditional.
1555 * On entry, cmdlist will point to command containing the IF, and optionally
1556 * the first command to execute (if brackets not found)
1557 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1558 * If ('s were found, execute all within that bracket
1559 * Command may optionally be followed by an ELSE - need to skip instructions
1560 * in the else using the same logic
1562 * FIXME: Much more syntax checking needed!
1565 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1567 int negate; /* Negate condition */
1568 int test; /* Condition evaluation result */
1569 WCHAR condition[MAX_PATH], *command, *s;
1570 static const WCHAR notW[] = {'n','o','t','\0'};
1571 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1572 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1573 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1574 static const WCHAR eqeqW[] = {'=','=','\0'};
1575 static const WCHAR parmI[] = {'/','I','\0'};
1576 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1578 negate = !lstrcmpiW(param1,notW);
1579 strcpyW(condition, (negate ? param2 : param1));
1580 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1582 if (!lstrcmpiW (condition, errlvlW)) {
1583 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL);
1584 WCHAR *endptr;
1585 long int param_int = strtolW(param, &endptr, 10);
1586 if (*endptr) {
1587 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1588 return;
1590 test = ((long int)errorlevel >= param_int);
1591 WCMD_parameter(p, 2+negate, &command, NULL);
1593 else if (!lstrcmpiW (condition, existW)) {
1594 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1595 WCMD_parameter(p, 2+negate, &command, NULL);
1597 else if (!lstrcmpiW (condition, defdW)) {
1598 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1599 WCMD_parameter(p, 2+negate, &command, NULL);
1601 else if ((s = strstrW (p, eqeqW))) {
1602 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1603 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1604 s += 2;
1605 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1606 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1607 test = caseInsensitive
1608 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1609 leftPart, leftPartEnd-leftPart+1,
1610 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1611 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1612 leftPart, leftPartEnd-leftPart+1,
1613 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1614 WCMD_parameter(s, 1, &command, NULL);
1616 else {
1617 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1618 return;
1621 /* Process rest of IF statement which is on the same line
1622 Note: This may process all or some of the cmdList (eg a GOTO) */
1623 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1626 /****************************************************************************
1627 * WCMD_move
1629 * Move a file, directory tree or wildcarded set of files.
1632 void WCMD_move (void)
1634 int status;
1635 WIN32_FIND_DATAW fd;
1636 HANDLE hff;
1637 WCHAR input[MAX_PATH];
1638 WCHAR output[MAX_PATH];
1639 WCHAR drive[10];
1640 WCHAR dir[MAX_PATH];
1641 WCHAR fname[MAX_PATH];
1642 WCHAR ext[MAX_PATH];
1644 if (param1[0] == 0x00) {
1645 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1646 return;
1649 /* If no destination supplied, assume current directory */
1650 if (param2[0] == 0x00) {
1651 strcpyW(param2, dotW);
1654 /* If 2nd parm is directory, then use original filename */
1655 /* Convert partial path to full path */
1656 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1657 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1658 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1659 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1661 /* Split into components */
1662 WCMD_splitpath(input, drive, dir, fname, ext);
1664 hff = FindFirstFileW(input, &fd);
1665 if (hff == INVALID_HANDLE_VALUE)
1666 return;
1668 do {
1669 WCHAR dest[MAX_PATH];
1670 WCHAR src[MAX_PATH];
1671 DWORD attribs;
1672 BOOL ok = TRUE;
1674 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1676 /* Build src & dest name */
1677 strcpyW(src, drive);
1678 strcatW(src, dir);
1680 /* See if dest is an existing directory */
1681 attribs = GetFileAttributesW(output);
1682 if (attribs != INVALID_FILE_ATTRIBUTES &&
1683 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1684 strcpyW(dest, output);
1685 strcatW(dest, slashW);
1686 strcatW(dest, fd.cFileName);
1687 } else {
1688 strcpyW(dest, output);
1691 strcatW(src, fd.cFileName);
1693 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1694 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1696 /* If destination exists, prompt unless /Y supplied */
1697 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1698 BOOL force = FALSE;
1699 WCHAR copycmd[MAXSTRING];
1700 DWORD len;
1702 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1703 if (strstrW (quals, parmNoY))
1704 force = FALSE;
1705 else if (strstrW (quals, parmY))
1706 force = TRUE;
1707 else {
1708 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1709 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1710 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1711 && ! lstrcmpiW (copycmd, parmY));
1714 /* Prompt if overwriting */
1715 if (!force) {
1716 WCHAR* question;
1718 /* Ask for confirmation */
1719 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1720 ok = WCMD_ask_confirm(question, FALSE, NULL);
1721 LocalFree(question);
1723 /* So delete the destination prior to the move */
1724 if (ok) {
1725 if (!DeleteFileW(dest)) {
1726 WCMD_print_error ();
1727 errorlevel = 1;
1728 ok = FALSE;
1734 if (ok) {
1735 status = MoveFileW(src, dest);
1736 } else {
1737 status = 1; /* Anything other than 0 to prevent error msg below */
1740 if (!status) {
1741 WCMD_print_error ();
1742 errorlevel = 1;
1744 } while (FindNextFileW(hff, &fd) != 0);
1746 FindClose(hff);
1749 /****************************************************************************
1750 * WCMD_pause
1752 * Suspend execution of a batch script until a key is typed
1755 void WCMD_pause (void)
1757 DWORD oldmode;
1758 BOOL have_console;
1759 DWORD count;
1760 WCHAR key;
1761 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1763 have_console = GetConsoleMode(hIn, &oldmode);
1764 if (have_console)
1765 SetConsoleMode(hIn, 0);
1767 WCMD_output_asis(anykey);
1768 WCMD_ReadFile(hIn, &key, 1, &count);
1769 if (have_console)
1770 SetConsoleMode(hIn, oldmode);
1773 /****************************************************************************
1774 * WCMD_remove_dir
1776 * Delete a directory.
1779 void WCMD_remove_dir (WCHAR *command) {
1781 int argno = 0;
1782 int argsProcessed = 0;
1783 WCHAR *argN = command;
1784 static const WCHAR parmS[] = {'/','S','\0'};
1785 static const WCHAR parmQ[] = {'/','Q','\0'};
1787 /* Loop through all args */
1788 while (argN) {
1789 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1790 if (argN && argN[0] != '/') {
1791 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1792 wine_dbgstr_w(quals));
1793 argsProcessed++;
1795 /* If subdirectory search not supplied, just try to remove
1796 and report error if it fails (eg if it contains a file) */
1797 if (strstrW (quals, parmS) == NULL) {
1798 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1800 /* Otherwise use ShFileOp to recursively remove a directory */
1801 } else {
1803 SHFILEOPSTRUCTW lpDir;
1805 /* Ask first */
1806 if (strstrW (quals, parmQ) == NULL) {
1807 BOOL ok;
1808 WCHAR question[MAXSTRING];
1809 static const WCHAR fmt[] = {'%','s',' ','\0'};
1811 /* Ask for confirmation */
1812 wsprintfW(question, fmt, thisArg);
1813 ok = WCMD_ask_confirm(question, TRUE, NULL);
1815 /* Abort if answer is 'N' */
1816 if (!ok) return;
1819 /* Do the delete */
1820 lpDir.hwnd = NULL;
1821 lpDir.pTo = NULL;
1822 lpDir.pFrom = thisArg;
1823 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1824 lpDir.wFunc = FO_DELETE;
1825 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1830 /* Handle no valid args */
1831 if (argsProcessed == 0) {
1832 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1833 return;
1838 /****************************************************************************
1839 * WCMD_rename
1841 * Rename a file.
1844 void WCMD_rename (void)
1846 int status;
1847 HANDLE hff;
1848 WIN32_FIND_DATAW fd;
1849 WCHAR input[MAX_PATH];
1850 WCHAR *dotDst = NULL;
1851 WCHAR drive[10];
1852 WCHAR dir[MAX_PATH];
1853 WCHAR fname[MAX_PATH];
1854 WCHAR ext[MAX_PATH];
1856 errorlevel = 0;
1858 /* Must be at least two args */
1859 if (param1[0] == 0x00 || param2[0] == 0x00) {
1860 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1861 errorlevel = 1;
1862 return;
1865 /* Destination cannot contain a drive letter or directory separator */
1866 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1867 SetLastError(ERROR_INVALID_PARAMETER);
1868 WCMD_print_error();
1869 errorlevel = 1;
1870 return;
1873 /* Convert partial path to full path */
1874 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1875 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1876 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1877 dotDst = strchrW(param2, '.');
1879 /* Split into components */
1880 WCMD_splitpath(input, drive, dir, fname, ext);
1882 hff = FindFirstFileW(input, &fd);
1883 if (hff == INVALID_HANDLE_VALUE)
1884 return;
1886 do {
1887 WCHAR dest[MAX_PATH];
1888 WCHAR src[MAX_PATH];
1889 WCHAR *dotSrc = NULL;
1890 int dirLen;
1892 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1894 /* FIXME: If dest name or extension is *, replace with filename/ext
1895 part otherwise use supplied name. This supports:
1896 ren *.fred *.jim
1897 ren jim.* fred.* etc
1898 However, windows has a more complex algorithm supporting eg
1899 ?'s and *'s mid name */
1900 dotSrc = strchrW(fd.cFileName, '.');
1902 /* Build src & dest name */
1903 strcpyW(src, drive);
1904 strcatW(src, dir);
1905 strcpyW(dest, src);
1906 dirLen = strlenW(src);
1907 strcatW(src, fd.cFileName);
1909 /* Build name */
1910 if (param2[0] == '*') {
1911 strcatW(dest, fd.cFileName);
1912 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1913 } else {
1914 strcatW(dest, param2);
1915 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1918 /* Build Extension */
1919 if (dotDst && (*(dotDst+1)=='*')) {
1920 if (dotSrc) strcatW(dest, dotSrc);
1921 } else if (dotDst) {
1922 if (dotDst) strcatW(dest, dotDst);
1925 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1926 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1928 status = MoveFileW(src, dest);
1930 if (!status) {
1931 WCMD_print_error ();
1932 errorlevel = 1;
1934 } while (FindNextFileW(hff, &fd) != 0);
1936 FindClose(hff);
1939 /*****************************************************************************
1940 * WCMD_dupenv
1942 * Make a copy of the environment.
1944 static WCHAR *WCMD_dupenv( const WCHAR *env )
1946 WCHAR *env_copy;
1947 int len;
1949 if( !env )
1950 return NULL;
1952 len = 0;
1953 while ( env[len] )
1954 len += (strlenW(&env[len]) + 1);
1956 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1957 if (!env_copy)
1959 WINE_ERR("out of memory\n");
1960 return env_copy;
1962 memcpy (env_copy, env, len*sizeof (WCHAR));
1963 env_copy[len] = 0;
1965 return env_copy;
1968 /*****************************************************************************
1969 * WCMD_setlocal
1971 * setlocal pushes the environment onto a stack
1972 * Save the environment as unicode so we don't screw anything up.
1974 void WCMD_setlocal (const WCHAR *s) {
1975 WCHAR *env;
1976 struct env_stack *env_copy;
1977 WCHAR cwd[MAX_PATH];
1979 /* DISABLEEXTENSIONS ignored */
1981 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1982 if( !env_copy )
1984 WINE_ERR ("out of memory\n");
1985 return;
1988 env = GetEnvironmentStringsW ();
1990 env_copy->strings = WCMD_dupenv (env);
1991 if (env_copy->strings)
1993 env_copy->next = saved_environment;
1994 saved_environment = env_copy;
1996 /* Save the current drive letter */
1997 GetCurrentDirectoryW(MAX_PATH, cwd);
1998 env_copy->u.cwd = cwd[0];
2000 else
2001 LocalFree (env_copy);
2003 FreeEnvironmentStringsW (env);
2007 /*****************************************************************************
2008 * WCMD_endlocal
2010 * endlocal pops the environment off a stack
2011 * Note: When searching for '=', search from WCHAR position 1, to handle
2012 * special internal environment variables =C:, =D: etc
2014 void WCMD_endlocal (void) {
2015 WCHAR *env, *old, *p;
2016 struct env_stack *temp;
2017 int len, n;
2019 if (!saved_environment)
2020 return;
2022 /* pop the old environment from the stack */
2023 temp = saved_environment;
2024 saved_environment = temp->next;
2026 /* delete the current environment, totally */
2027 env = GetEnvironmentStringsW ();
2028 old = WCMD_dupenv (GetEnvironmentStringsW ());
2029 len = 0;
2030 while (old[len]) {
2031 n = strlenW(&old[len]) + 1;
2032 p = strchrW(&old[len] + 1, '=');
2033 if (p)
2035 *p++ = 0;
2036 SetEnvironmentVariableW (&old[len], NULL);
2038 len += n;
2040 LocalFree (old);
2041 FreeEnvironmentStringsW (env);
2043 /* restore old environment */
2044 env = temp->strings;
2045 len = 0;
2046 while (env[len]) {
2047 n = strlenW(&env[len]) + 1;
2048 p = strchrW(&env[len] + 1, '=');
2049 if (p)
2051 *p++ = 0;
2052 SetEnvironmentVariableW (&env[len], p);
2054 len += n;
2057 /* Restore current drive letter */
2058 if (IsCharAlphaW(temp->u.cwd)) {
2059 WCHAR envvar[4];
2060 WCHAR cwd[MAX_PATH];
2061 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2063 wsprintfW(envvar, fmt, temp->u.cwd);
2064 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2065 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2066 SetCurrentDirectoryW(cwd);
2070 LocalFree (env);
2071 LocalFree (temp);
2074 /*****************************************************************************
2075 * WCMD_setshow_default
2077 * Set/Show the current default directory
2080 void WCMD_setshow_default (const WCHAR *command) {
2082 BOOL status;
2083 WCHAR string[1024];
2084 WCHAR cwd[1024];
2085 WCHAR *pos;
2086 WIN32_FIND_DATAW fd;
2087 HANDLE hff;
2088 static const WCHAR parmD[] = {'/','D','\0'};
2090 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2092 /* Skip /D and trailing whitespace if on the front of the command line */
2093 if (CompareStringW(LOCALE_USER_DEFAULT,
2094 NORM_IGNORECASE | SORT_STRINGSORT,
2095 command, 2, parmD, -1) == CSTR_EQUAL) {
2096 command += 2;
2097 while (*command && (*command==' ' || *command=='\t'))
2098 command++;
2101 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2102 if (strlenW(command) == 0) {
2103 strcatW (cwd, newline);
2104 WCMD_output_asis (cwd);
2106 else {
2107 /* Remove any double quotes, which may be in the
2108 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2109 pos = string;
2110 while (*command) {
2111 if (*command != '"') *pos++ = *command;
2112 command++;
2114 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2115 pos--;
2116 *pos = 0x00;
2118 /* Search for appropriate directory */
2119 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2120 hff = FindFirstFileW(string, &fd);
2121 if (hff != INVALID_HANDLE_VALUE) {
2122 do {
2123 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2124 WCHAR fpath[MAX_PATH];
2125 WCHAR drive[10];
2126 WCHAR dir[MAX_PATH];
2127 WCHAR fname[MAX_PATH];
2128 WCHAR ext[MAX_PATH];
2129 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2131 /* Convert path into actual directory spec */
2132 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2133 WCMD_splitpath(fpath, drive, dir, fname, ext);
2135 /* Rebuild path */
2136 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2137 break;
2139 } while (FindNextFileW(hff, &fd) != 0);
2140 FindClose(hff);
2143 /* Change to that directory */
2144 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2146 status = SetCurrentDirectoryW(string);
2147 if (!status) {
2148 errorlevel = 1;
2149 WCMD_print_error ();
2150 return;
2151 } else {
2153 /* Save away the actual new directory, to store as current location */
2154 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2156 /* Restore old directory if drive letter would change, and
2157 CD x:\directory /D (or pushd c:\directory) not supplied */
2158 if ((strstrW(quals, parmD) == NULL) &&
2159 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2160 SetCurrentDirectoryW(cwd);
2164 /* Set special =C: type environment variable, for drive letter of
2165 change of directory, even if path was restored due to missing
2166 /D (allows changing drive letter when not resident on that
2167 drive */
2168 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2169 WCHAR env[4];
2170 strcpyW(env, equalW);
2171 memcpy(env+1, string, 2 * sizeof(WCHAR));
2172 env[3] = 0x00;
2173 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2174 SetEnvironmentVariableW(env, string);
2178 return;
2181 /****************************************************************************
2182 * WCMD_setshow_date
2184 * Set/Show the system date
2185 * FIXME: Can't change date yet
2188 void WCMD_setshow_date (void) {
2190 WCHAR curdate[64], buffer[64];
2191 DWORD count;
2192 static const WCHAR parmT[] = {'/','T','\0'};
2194 if (strlenW(param1) == 0) {
2195 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2196 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2197 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2198 if (strstrW (quals, parmT) == NULL) {
2199 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2200 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2201 if (count > 2) {
2202 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2206 else WCMD_print_error ();
2208 else {
2209 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2213 /****************************************************************************
2214 * WCMD_compare
2216 static int WCMD_compare( const void *a, const void *b )
2218 int r;
2219 const WCHAR * const *str_a = a, * const *str_b = b;
2220 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2221 *str_a, -1, *str_b, -1 );
2222 if( r == CSTR_LESS_THAN ) return -1;
2223 if( r == CSTR_GREATER_THAN ) return 1;
2224 return 0;
2227 /****************************************************************************
2228 * WCMD_setshow_sortenv
2230 * sort variables into order for display
2231 * Optionally only display those who start with a stub
2232 * returns the count displayed
2234 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2236 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2237 const WCHAR **str;
2239 if (stub) stublen = strlenW(stub);
2241 /* count the number of strings, and the total length */
2242 while ( s[len] ) {
2243 len += (strlenW(&s[len]) + 1);
2244 count++;
2247 /* add the strings to an array */
2248 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2249 if( !str )
2250 return 0;
2251 str[0] = s;
2252 for( i=1; i<count; i++ )
2253 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2255 /* sort the array */
2256 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2258 /* print it */
2259 for( i=0; i<count; i++ ) {
2260 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2261 NORM_IGNORECASE | SORT_STRINGSORT,
2262 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2263 /* Don't display special internal variables */
2264 if (str[i][0] != '=') {
2265 WCMD_output_asis(str[i]);
2266 WCMD_output_asis(newline);
2267 displayedcount++;
2272 LocalFree( str );
2273 return displayedcount;
2276 /****************************************************************************
2277 * WCMD_setshow_env
2279 * Set/Show the environment variables
2282 void WCMD_setshow_env (WCHAR *s) {
2284 LPVOID env;
2285 WCHAR *p;
2286 int status;
2287 static const WCHAR parmP[] = {'/','P','\0'};
2289 if (param1[0] == 0x00 && quals[0] == 0x00) {
2290 env = GetEnvironmentStringsW();
2291 WCMD_setshow_sortenv( env, NULL );
2292 return;
2295 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2296 if (CompareStringW(LOCALE_USER_DEFAULT,
2297 NORM_IGNORECASE | SORT_STRINGSORT,
2298 s, 2, parmP, -1) == CSTR_EQUAL) {
2299 WCHAR string[MAXSTRING];
2300 DWORD count;
2302 s += 2;
2303 while (*s && (*s==' ' || *s=='\t')) s++;
2304 if (*s=='\"')
2305 WCMD_strip_quotes(s);
2307 /* If no parameter, or no '=' sign, return an error */
2308 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2309 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2310 return;
2313 /* Output the prompt */
2314 *p++ = '\0';
2315 if (strlenW(p) != 0) WCMD_output_asis(p);
2317 /* Read the reply */
2318 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2319 if (count > 1) {
2320 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2321 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2322 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2323 wine_dbgstr_w(string));
2324 status = SetEnvironmentVariableW(s, string);
2327 } else {
2328 DWORD gle;
2330 if (*s=='\"')
2331 WCMD_strip_quotes(s);
2332 p = strchrW (s, '=');
2333 if (p == NULL) {
2334 env = GetEnvironmentStringsW();
2335 if (WCMD_setshow_sortenv( env, s ) == 0) {
2336 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2337 errorlevel = 1;
2339 return;
2341 *p++ = '\0';
2343 if (strlenW(p) == 0) p = NULL;
2344 status = SetEnvironmentVariableW(s, p);
2345 gle = GetLastError();
2346 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2347 errorlevel = 1;
2348 } else if ((!status)) WCMD_print_error();
2352 /****************************************************************************
2353 * WCMD_setshow_path
2355 * Set/Show the path environment variable
2358 void WCMD_setshow_path (const WCHAR *command) {
2360 WCHAR string[1024];
2361 DWORD status;
2362 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2363 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2365 if (strlenW(param1) == 0) {
2366 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2367 if (status != 0) {
2368 WCMD_output_asis ( pathEqW);
2369 WCMD_output_asis ( string);
2370 WCMD_output_asis ( newline);
2372 else {
2373 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2376 else {
2377 if (*command == '=') command++; /* Skip leading '=' */
2378 status = SetEnvironmentVariableW(pathW, command);
2379 if (!status) WCMD_print_error();
2383 /****************************************************************************
2384 * WCMD_setshow_prompt
2386 * Set or show the command prompt.
2389 void WCMD_setshow_prompt (void) {
2391 WCHAR *s;
2392 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2394 if (strlenW(param1) == 0) {
2395 SetEnvironmentVariableW(promptW, NULL);
2397 else {
2398 s = param1;
2399 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2400 if (strlenW(s) == 0) {
2401 SetEnvironmentVariableW(promptW, NULL);
2403 else SetEnvironmentVariableW(promptW, s);
2407 /****************************************************************************
2408 * WCMD_setshow_time
2410 * Set/Show the system time
2411 * FIXME: Can't change time yet
2414 void WCMD_setshow_time (void) {
2416 WCHAR curtime[64], buffer[64];
2417 DWORD count;
2418 SYSTEMTIME st;
2419 static const WCHAR parmT[] = {'/','T','\0'};
2421 if (strlenW(param1) == 0) {
2422 GetLocalTime(&st);
2423 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2424 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2425 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2426 if (strstrW (quals, parmT) == NULL) {
2427 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2428 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2429 if (count > 2) {
2430 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2434 else WCMD_print_error ();
2436 else {
2437 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2441 /****************************************************************************
2442 * WCMD_shift
2444 * Shift batch parameters.
2445 * Optional /n says where to start shifting (n=0-8)
2448 void WCMD_shift (const WCHAR *command) {
2449 int start;
2451 if (context != NULL) {
2452 WCHAR *pos = strchrW(command, '/');
2453 int i;
2455 if (pos == NULL) {
2456 start = 0;
2457 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2458 start = (*(pos+1) - '0');
2459 } else {
2460 SetLastError(ERROR_INVALID_PARAMETER);
2461 WCMD_print_error();
2462 return;
2465 WINE_TRACE("Shifting variables, starting at %d\n", start);
2466 for (i=start;i<=8;i++) {
2467 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2469 context -> shift_count[9] = context -> shift_count[9] + 1;
2474 /****************************************************************************
2475 * WCMD_title
2477 * Set the console title
2479 void WCMD_title (const WCHAR *command) {
2480 SetConsoleTitleW(command);
2483 /****************************************************************************
2484 * WCMD_type
2486 * Copy a file to standard output.
2489 void WCMD_type (WCHAR *command) {
2491 int argno = 0;
2492 WCHAR *argN = command;
2493 BOOL writeHeaders = FALSE;
2495 if (param1[0] == 0x00) {
2496 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2497 return;
2500 if (param2[0] != 0x00) writeHeaders = TRUE;
2502 /* Loop through all args */
2503 errorlevel = 0;
2504 while (argN) {
2505 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2507 HANDLE h;
2508 WCHAR buffer[512];
2509 DWORD count;
2511 if (!argN) break;
2513 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2514 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2515 FILE_ATTRIBUTE_NORMAL, NULL);
2516 if (h == INVALID_HANDLE_VALUE) {
2517 WCMD_print_error ();
2518 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2519 errorlevel = 1;
2520 } else {
2521 if (writeHeaders) {
2522 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2523 WCMD_output(fmt, thisArg);
2525 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2526 if (count == 0) break; /* ReadFile reports success on EOF! */
2527 buffer[count] = 0;
2528 WCMD_output_asis (buffer);
2530 CloseHandle (h);
2535 /****************************************************************************
2536 * WCMD_more
2538 * Output either a file or stdin to screen in pages
2541 void WCMD_more (WCHAR *command) {
2543 int argno = 0;
2544 WCHAR *argN = command;
2545 WCHAR moreStr[100];
2546 WCHAR moreStrPage[100];
2547 WCHAR buffer[512];
2548 DWORD count;
2549 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2550 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2551 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2552 ')',' ','-','-','\n','\0'};
2553 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2555 /* Prefix the NLS more with '-- ', then load the text */
2556 errorlevel = 0;
2557 strcpyW(moreStr, moreStart);
2558 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2559 (sizeof(moreStr)/sizeof(WCHAR))-3);
2561 if (param1[0] == 0x00) {
2563 /* Wine implements pipes via temporary files, and hence stdin is
2564 effectively reading from the file. This means the prompts for
2565 more are satisfied by the next line from the input (file). To
2566 avoid this, ensure stdin is to the console */
2567 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2568 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2569 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2570 FILE_ATTRIBUTE_NORMAL, 0);
2571 WINE_TRACE("No parms - working probably in pipe mode\n");
2572 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2574 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2575 once you get in this bit unless due to a pipe, its going to end badly... */
2576 wsprintfW(moreStrPage, moreFmt, moreStr);
2578 WCMD_enter_paged_mode(moreStrPage);
2579 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2580 if (count == 0) break; /* ReadFile reports success on EOF! */
2581 buffer[count] = 0;
2582 WCMD_output_asis (buffer);
2584 WCMD_leave_paged_mode();
2586 /* Restore stdin to what it was */
2587 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2588 CloseHandle(hConIn);
2590 return;
2591 } else {
2592 BOOL needsPause = FALSE;
2594 /* Loop through all args */
2595 WINE_TRACE("Parms supplied - working through each file\n");
2596 WCMD_enter_paged_mode(moreStrPage);
2598 while (argN) {
2599 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2600 HANDLE h;
2602 if (!argN) break;
2604 if (needsPause) {
2606 /* Wait */
2607 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2608 WCMD_leave_paged_mode();
2609 WCMD_output_asis(moreStrPage);
2610 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2611 WCMD_enter_paged_mode(moreStrPage);
2615 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2616 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2617 FILE_ATTRIBUTE_NORMAL, NULL);
2618 if (h == INVALID_HANDLE_VALUE) {
2619 WCMD_print_error ();
2620 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2621 errorlevel = 1;
2622 } else {
2623 ULONG64 curPos = 0;
2624 ULONG64 fileLen = 0;
2625 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2627 /* Get the file size */
2628 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2629 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2631 needsPause = TRUE;
2632 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2633 if (count == 0) break; /* ReadFile reports success on EOF! */
2634 buffer[count] = 0;
2635 curPos += count;
2637 /* Update % count (would be used in WCMD_output_asis as prompt) */
2638 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2640 WCMD_output_asis (buffer);
2642 CloseHandle (h);
2646 WCMD_leave_paged_mode();
2650 /****************************************************************************
2651 * WCMD_verify
2653 * Display verify flag.
2654 * FIXME: We don't actually do anything with the verify flag other than toggle
2655 * it...
2658 void WCMD_verify (const WCHAR *command) {
2660 int count;
2662 count = strlenW(command);
2663 if (count == 0) {
2664 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2665 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2666 return;
2668 if (lstrcmpiW(command, onW) == 0) {
2669 verify_mode = TRUE;
2670 return;
2672 else if (lstrcmpiW(command, offW) == 0) {
2673 verify_mode = FALSE;
2674 return;
2676 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2679 /****************************************************************************
2680 * WCMD_version
2682 * Display version info.
2685 void WCMD_version (void) {
2687 WCMD_output_asis (version_string);
2691 /****************************************************************************
2692 * WCMD_volume
2694 * Display volume information (set_label = FALSE)
2695 * Additionally set volume label (set_label = TRUE)
2696 * Returns 1 on success, 0 otherwise
2699 int WCMD_volume(BOOL set_label, const WCHAR *path)
2701 DWORD count, serial;
2702 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2703 BOOL status;
2705 if (strlenW(path) == 0) {
2706 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2707 if (!status) {
2708 WCMD_print_error ();
2709 return 0;
2711 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2712 &serial, NULL, NULL, NULL, 0);
2714 else {
2715 static const WCHAR fmt[] = {'%','s','\\','\0'};
2716 if ((path[1] != ':') || (strlenW(path) != 2)) {
2717 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2718 return 0;
2720 wsprintfW (curdir, fmt, path);
2721 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2722 &serial, NULL,
2723 NULL, NULL, 0);
2725 if (!status) {
2726 WCMD_print_error ();
2727 return 0;
2729 if (label[0] != '\0') {
2730 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
2731 curdir[0], label);
2733 else {
2734 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
2735 curdir[0]);
2737 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
2738 HIWORD(serial), LOWORD(serial));
2739 if (set_label) {
2740 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2741 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2742 if (count > 1) {
2743 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2744 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2746 if (strlenW(path) != 0) {
2747 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2749 else {
2750 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2753 return 1;
2756 /**************************************************************************
2757 * WCMD_exit
2759 * Exit either the process, or just this batch program
2763 void WCMD_exit (CMD_LIST **cmdList) {
2765 static const WCHAR parmB[] = {'/','B','\0'};
2766 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2768 if (context && lstrcmpiW(quals, parmB) == 0) {
2769 errorlevel = rc;
2770 context -> skip_rest = TRUE;
2771 *cmdList = NULL;
2772 } else {
2773 ExitProcess(rc);
2778 /*****************************************************************************
2779 * WCMD_assoc
2781 * Lists or sets file associations (assoc = TRUE)
2782 * Lists or sets file types (assoc = FALSE)
2784 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2786 HKEY key;
2787 DWORD accessOptions = KEY_READ;
2788 WCHAR *newValue;
2789 LONG rc = ERROR_SUCCESS;
2790 WCHAR keyValue[MAXSTRING];
2791 DWORD valueLen = MAXSTRING;
2792 HKEY readKey;
2793 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2794 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2796 /* See if parameter includes '=' */
2797 errorlevel = 0;
2798 newValue = strchrW(command, '=');
2799 if (newValue) accessOptions |= KEY_WRITE;
2801 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2802 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2803 accessOptions, &key) != ERROR_SUCCESS) {
2804 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2805 return;
2808 /* If no parameters then list all associations */
2809 if (*command == 0x00) {
2810 int index = 0;
2812 /* Enumerate all the keys */
2813 while (rc != ERROR_NO_MORE_ITEMS) {
2814 WCHAR keyName[MAXSTRING];
2815 DWORD nameLen;
2817 /* Find the next value */
2818 nameLen = MAXSTRING;
2819 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2821 if (rc == ERROR_SUCCESS) {
2823 /* Only interested in extension ones if assoc, or others
2824 if not assoc */
2825 if ((keyName[0] == '.' && assoc) ||
2826 (!(keyName[0] == '.') && (!assoc)))
2828 WCHAR subkey[MAXSTRING];
2829 strcpyW(subkey, keyName);
2830 if (!assoc) strcatW(subkey, shOpCmdW);
2832 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2834 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2835 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2836 WCMD_output_asis(keyName);
2837 WCMD_output_asis(equalW);
2838 /* If no default value found, leave line empty after '=' */
2839 if (rc == ERROR_SUCCESS) {
2840 WCMD_output_asis(keyValue);
2842 WCMD_output_asis(newline);
2843 RegCloseKey(readKey);
2849 } else {
2851 /* Parameter supplied - if no '=' on command line, its a query */
2852 if (newValue == NULL) {
2853 WCHAR *space;
2854 WCHAR subkey[MAXSTRING];
2856 /* Query terminates the parameter at the first space */
2857 strcpyW(keyValue, command);
2858 space = strchrW(keyValue, ' ');
2859 if (space) *space=0x00;
2861 /* Set up key name */
2862 strcpyW(subkey, keyValue);
2863 if (!assoc) strcatW(subkey, shOpCmdW);
2865 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2867 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2868 WCMD_output_asis(command);
2869 WCMD_output_asis(equalW);
2870 /* If no default value found, leave line empty after '=' */
2871 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2872 WCMD_output_asis(newline);
2873 RegCloseKey(readKey);
2875 } else {
2876 WCHAR msgbuffer[MAXSTRING];
2878 /* Load the translated 'File association not found' */
2879 if (assoc) {
2880 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2881 } else {
2882 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2884 WCMD_output_stderr(msgbuffer, keyValue);
2885 errorlevel = 2;
2888 /* Not a query - its a set or clear of a value */
2889 } else {
2891 WCHAR subkey[MAXSTRING];
2893 /* Get pointer to new value */
2894 *newValue = 0x00;
2895 newValue++;
2897 /* Set up key name */
2898 strcpyW(subkey, command);
2899 if (!assoc) strcatW(subkey, shOpCmdW);
2901 /* If nothing after '=' then clear value - only valid for ASSOC */
2902 if (*newValue == 0x00) {
2904 if (assoc) rc = RegDeleteKeyW(key, command);
2905 if (assoc && rc == ERROR_SUCCESS) {
2906 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2908 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2909 WCMD_print_error();
2910 errorlevel = 2;
2912 } else {
2913 WCHAR msgbuffer[MAXSTRING];
2915 /* Load the translated 'File association not found' */
2916 if (assoc) {
2917 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2918 sizeof(msgbuffer)/sizeof(WCHAR));
2919 } else {
2920 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2921 sizeof(msgbuffer)/sizeof(WCHAR));
2923 WCMD_output_stderr(msgbuffer, keyValue);
2924 errorlevel = 2;
2927 /* It really is a set value = contents */
2928 } else {
2929 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2930 accessOptions, NULL, &readKey, NULL);
2931 if (rc == ERROR_SUCCESS) {
2932 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2933 (LPBYTE)newValue,
2934 sizeof(WCHAR) * (strlenW(newValue) + 1));
2935 RegCloseKey(readKey);
2938 if (rc != ERROR_SUCCESS) {
2939 WCMD_print_error();
2940 errorlevel = 2;
2941 } else {
2942 WCMD_output_asis(command);
2943 WCMD_output_asis(equalW);
2944 WCMD_output_asis(newValue);
2945 WCMD_output_asis(newline);
2951 /* Clean up */
2952 RegCloseKey(key);
2955 /****************************************************************************
2956 * WCMD_color
2958 * Colors the terminal screen.
2961 void WCMD_color (void) {
2963 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2964 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2966 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2967 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
2968 return;
2971 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2973 COORD topLeft;
2974 DWORD screenSize;
2975 DWORD color = 0;
2977 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2979 topLeft.X = 0;
2980 topLeft.Y = 0;
2982 /* Convert the color hex digits */
2983 if (param1[0] == 0x00) {
2984 color = defaultColor;
2985 } else {
2986 color = strtoulW(param1, NULL, 16);
2989 /* Fail if fg == bg color */
2990 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2991 errorlevel = 1;
2992 return;
2995 /* Set the current screen contents and ensure all future writes
2996 remain this color */
2997 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2998 SetConsoleTextAttribute(hStdOut, color);