cmd: A successful set should reset the errorlevel.
[wine/multimedia.git] / programs / cmd / builtins.c
bloba9e74736d72c9e3386c90643a08910c47e2f97a0
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 {'S','T','A','R','T','\0'},
78 {'T','I','M','E','\0'},
79 {'T','I','T','L','E','\0'},
80 {'T','Y','P','E','\0'},
81 {'V','E','R','I','F','Y','\0'},
82 {'V','E','R','\0'},
83 {'V','O','L','\0'},
84 {'E','N','D','L','O','C','A','L','\0'},
85 {'S','E','T','L','O','C','A','L','\0'},
86 {'P','U','S','H','D','\0'},
87 {'P','O','P','D','\0'},
88 {'A','S','S','O','C','\0'},
89 {'C','O','L','O','R','\0'},
90 {'F','T','Y','P','E','\0'},
91 {'M','O','R','E','\0'},
92 {'C','H','O','I','C','E','\0'},
93 {'E','X','I','T','\0'}
95 static const WCHAR externals[][10] = {
96 {'A','T','T','R','I','B','\0'},
97 {'X','C','O','P','Y','\0'}
99 static const WCHAR fslashW[] = {'/','\0'};
100 static const WCHAR onW[] = {'O','N','\0'};
101 static const WCHAR offW[] = {'O','F','F','\0'};
102 static const WCHAR parmY[] = {'/','Y','\0'};
103 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
105 static HINSTANCE hinst;
106 static struct env_stack *saved_environment;
107 static BOOL verify_mode = FALSE;
109 /**************************************************************************
110 * WCMD_ask_confirm
112 * Issue a message and ask for confirmation, waiting on a valid 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 BOOL *optionAll) {
122 UINT msgid;
123 WCHAR confirm[MAXSTRING];
124 WCHAR options[MAXSTRING];
125 WCHAR Ybuffer[MAXSTRING];
126 WCHAR Nbuffer[MAXSTRING];
127 WCHAR Abuffer[MAXSTRING];
128 WCHAR answer[MAX_PATH] = {'\0'};
129 DWORD count = 0;
131 /* Load the translated valid answers */
132 if (showSureText)
133 LoadStringW(hinst, WCMD_CONFIRM, confirm, sizeof(confirm)/sizeof(WCHAR));
134 msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
135 LoadStringW(hinst, msgid, options, sizeof(options)/sizeof(WCHAR));
136 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
137 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
138 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
140 /* Loop waiting on a valid answer */
141 if (optionAll)
142 *optionAll = FALSE;
143 while (1)
145 WCMD_output_asis (message);
146 if (showSureText)
147 WCMD_output_asis (confirm);
148 WCMD_output_asis (options);
149 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
150 answer[0] = toupperW(answer[0]);
151 if (answer[0] == Ybuffer[0])
152 return TRUE;
153 if (answer[0] == Nbuffer[0])
154 return FALSE;
155 if (optionAll && answer[0] == Abuffer[0])
157 *optionAll = TRUE;
158 return TRUE;
163 /****************************************************************************
164 * WCMD_clear_screen
166 * Clear the terminal screen.
169 void WCMD_clear_screen (void) {
171 /* Emulate by filling the screen from the top left to bottom right with
172 spaces, then moving the cursor to the top left afterwards */
173 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
174 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
176 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
178 COORD topLeft;
179 DWORD screenSize;
181 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
183 topLeft.X = 0;
184 topLeft.Y = 0;
185 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
186 SetConsoleCursorPosition(hStdOut, topLeft);
190 /****************************************************************************
191 * WCMD_change_tty
193 * Change the default i/o device (ie redirect STDin/STDout).
196 void WCMD_change_tty (void) {
198 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
202 /****************************************************************************
203 * WCMD_choice
207 void WCMD_choice (const WCHAR * command) {
209 static const WCHAR bellW[] = {7,0};
210 static const WCHAR commaW[] = {',',0};
211 static const WCHAR bracket_open[] = {'[',0};
212 static const WCHAR bracket_close[] = {']','?',0};
213 WCHAR answer[16];
214 WCHAR buffer[16];
215 WCHAR *ptr = NULL;
216 WCHAR *opt_c = NULL;
217 WCHAR *my_command = NULL;
218 WCHAR opt_default = 0;
219 DWORD opt_timeout = 0;
220 DWORD count;
221 DWORD oldmode;
222 DWORD have_console;
223 BOOL opt_n = FALSE;
224 BOOL opt_s = FALSE;
226 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
227 errorlevel = 0;
229 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
230 if (!my_command)
231 return;
233 ptr = WCMD_skip_leading_spaces(my_command);
234 while (*ptr == '/') {
235 switch (toupperW(ptr[1])) {
236 case 'C':
237 ptr += 2;
238 /* the colon is optional */
239 if (*ptr == ':')
240 ptr++;
242 if (!*ptr || isspaceW(*ptr)) {
243 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
244 HeapFree(GetProcessHeap(), 0, my_command);
245 return;
248 /* remember the allowed keys (overwrite previous /C option) */
249 opt_c = ptr;
250 while (*ptr && (!isspaceW(*ptr)))
251 ptr++;
253 if (*ptr) {
254 /* terminate allowed chars */
255 *ptr = 0;
256 ptr = WCMD_skip_leading_spaces(&ptr[1]);
258 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
259 break;
261 case 'N':
262 opt_n = TRUE;
263 ptr = WCMD_skip_leading_spaces(&ptr[2]);
264 break;
266 case 'S':
267 opt_s = TRUE;
268 ptr = WCMD_skip_leading_spaces(&ptr[2]);
269 break;
271 case 'T':
272 ptr = &ptr[2];
273 /* the colon is optional */
274 if (*ptr == ':')
275 ptr++;
277 opt_default = *ptr++;
279 if (!opt_default || (*ptr != ',')) {
280 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
281 HeapFree(GetProcessHeap(), 0, my_command);
282 return;
284 ptr++;
286 count = 0;
287 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
288 count++;
289 ptr++;
292 answer[count] = 0;
293 opt_timeout = atoiW(answer);
295 ptr = WCMD_skip_leading_spaces(ptr);
296 break;
298 default:
299 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
300 HeapFree(GetProcessHeap(), 0, my_command);
301 return;
305 if (opt_timeout)
306 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
308 if (have_console)
309 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
311 /* use default keys, when needed: localized versions of "Y"es and "No" */
312 if (!opt_c) {
313 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
314 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
315 opt_c = buffer;
316 buffer[2] = 0;
319 /* print the question, when needed */
320 if (*ptr)
321 WCMD_output_asis(ptr);
323 if (!opt_s) {
324 struprW(opt_c);
325 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
328 if (!opt_n) {
329 /* print a list of all allowed answers inside brackets */
330 WCMD_output_asis(bracket_open);
331 ptr = opt_c;
332 answer[1] = 0;
333 while ((answer[0] = *ptr++)) {
334 WCMD_output_asis(answer);
335 if (*ptr)
336 WCMD_output_asis(commaW);
338 WCMD_output_asis(bracket_close);
341 while (TRUE) {
343 /* FIXME: Add support for option /T */
344 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
346 if (!opt_s)
347 answer[0] = toupperW(answer[0]);
349 ptr = strchrW(opt_c, answer[0]);
350 if (ptr) {
351 WCMD_output_asis(answer);
352 WCMD_output_asis(newlineW);
353 if (have_console)
354 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
356 errorlevel = (ptr - opt_c) + 1;
357 WINE_TRACE("answer: %d\n", errorlevel);
358 HeapFree(GetProcessHeap(), 0, my_command);
359 return;
361 else
363 /* key not allowed: play the bell */
364 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
365 WCMD_output_asis(bellW);
370 /****************************************************************************
371 * WCMD_copy
373 * Copy a file or wildcarded set.
374 * FIXME: Add support for a+b+c type syntax
377 void WCMD_copy (void) {
379 WIN32_FIND_DATAW fd;
380 HANDLE hff;
381 BOOL force, status;
382 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
383 DWORD len;
384 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
385 BOOL copyToDir = FALSE;
386 WCHAR srcspec[MAX_PATH];
387 DWORD attribs;
388 WCHAR drive[10];
389 WCHAR dir[MAX_PATH];
390 WCHAR fname[MAX_PATH];
391 WCHAR ext[MAX_PATH];
393 if (param1[0] == 0x00) {
394 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
395 return;
398 /* Convert source into full spec */
399 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
400 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
401 if (srcpath[strlenW(srcpath) - 1] == '\\')
402 srcpath[strlenW(srcpath) - 1] = '\0';
404 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
405 attribs = GetFileAttributesW(srcpath);
406 } else {
407 attribs = 0;
409 strcpyW(srcspec, srcpath);
411 /* If a directory, then add \* on the end when searching */
412 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
413 strcatW(srcpath, slashW);
414 strcatW(srcspec, slashW);
415 strcatW(srcspec, starW);
416 } else {
417 WCMD_splitpath(srcpath, drive, dir, fname, ext);
418 strcpyW(srcpath, drive);
419 strcatW(srcpath, dir);
422 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
424 /* If no destination supplied, assume current directory */
425 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
426 if (param2[0] == 0x00) {
427 strcpyW(param2, dotW);
430 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
431 if (outpath[strlenW(outpath) - 1] == '\\')
432 outpath[strlenW(outpath) - 1] = '\0';
433 attribs = GetFileAttributesW(outpath);
434 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
435 strcatW (outpath, slashW);
436 copyToDir = TRUE;
438 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
439 wine_dbgstr_w(outpath), copyToDir);
441 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
442 if (strstrW (quals, parmNoY))
443 force = FALSE;
444 else if (strstrW (quals, parmY))
445 force = TRUE;
446 else {
447 /* By default, we will force the overwrite in batch mode and ask for
448 * confirmation in interactive mode. */
449 force = !!context;
451 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
452 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
453 * default behavior. */
454 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
455 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
456 if (!lstrcmpiW (copycmd, parmY))
457 force = TRUE;
458 else if (!lstrcmpiW (copycmd, parmNoY))
459 force = FALSE;
463 /* Loop through all source files */
464 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
465 hff = FindFirstFileW(srcspec, &fd);
466 if (hff != INVALID_HANDLE_VALUE) {
467 do {
468 WCHAR outname[MAX_PATH];
469 WCHAR srcname[MAX_PATH];
470 BOOL overwrite = force;
472 /* Destination is either supplied filename, or source name in
473 supplied destination directory */
474 strcpyW(outname, outpath);
475 if (copyToDir) strcatW(outname, fd.cFileName);
476 strcpyW(srcname, srcpath);
477 strcatW(srcname, fd.cFileName);
479 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
480 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
482 /* Skip . and .., and directories */
483 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
484 overwrite = FALSE;
485 WINE_TRACE("Skipping directories\n");
488 /* Prompt before overwriting */
489 else if (!overwrite) {
490 attribs = GetFileAttributesW(outname);
491 if (attribs != INVALID_FILE_ATTRIBUTES) {
492 WCHAR* question;
493 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
494 overwrite = WCMD_ask_confirm(question, FALSE, NULL);
495 LocalFree(question);
497 else overwrite = TRUE;
500 /* Do the copy as appropriate */
501 if (overwrite) {
502 status = CopyFileW(srcname, outname, FALSE);
503 if (!status) WCMD_print_error ();
506 } while (FindNextFileW(hff, &fd) != 0);
507 FindClose (hff);
508 } else {
509 WCMD_print_error ();
513 /****************************************************************************
514 * WCMD_create_dir
516 * Create a directory (and, if needed, any intermediate directories).
518 * Modifies its argument by replacing slashes temporarily with nulls.
521 static BOOL create_full_path(WCHAR* path)
523 WCHAR *p, *start;
525 /* don't mess with drive letter portion of path, if any */
526 start = path;
527 if (path[1] == ':')
528 start = path+2;
530 /* Strip trailing slashes. */
531 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
532 *p = 0;
534 /* Step through path, creating intermediate directories as needed. */
535 /* First component includes drive letter, if any. */
536 p = start;
537 for (;;) {
538 DWORD rv;
539 /* Skip to end of component */
540 while (*p == '\\') p++;
541 while (*p && *p != '\\') p++;
542 if (!*p) {
543 /* path is now the original full path */
544 return CreateDirectoryW(path, NULL);
546 /* Truncate path, create intermediate directory, and restore path */
547 *p = 0;
548 rv = CreateDirectoryW(path, NULL);
549 *p = '\\';
550 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
551 return FALSE;
553 /* notreached */
554 return FALSE;
557 void WCMD_create_dir (WCHAR *command) {
558 int argno = 0;
559 WCHAR *argN = command;
561 if (param1[0] == 0x00) {
562 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
563 return;
565 /* Loop through all args */
566 while (TRUE) {
567 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL);
568 if (!argN) break;
569 if (!create_full_path(thisArg)) {
570 WCMD_print_error ();
571 errorlevel = 1;
576 /* Parse the /A options given by the user on the commandline
577 * into a bitmask of wanted attributes (*wantSet),
578 * and a bitmask of unwanted attributes (*wantClear).
580 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
581 static const WCHAR parmA[] = {'/','A','\0'};
582 WCHAR *p;
584 /* both are strictly 'out' parameters */
585 *wantSet=0;
586 *wantClear=0;
588 /* For each /A argument */
589 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
590 /* Skip /A itself */
591 p += 2;
593 /* Skip optional : */
594 if (*p == ':') p++;
596 /* For each of the attribute specifier chars to this /A option */
597 for (; *p != 0 && *p != '/'; p++) {
598 BOOL negate = FALSE;
599 DWORD mask = 0;
601 if (*p == '-') {
602 negate=TRUE;
603 p++;
606 /* Convert the attribute specifier to a bit in one of the masks */
607 switch (*p) {
608 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
609 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
610 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
611 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
612 default:
613 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
615 if (negate)
616 *wantClear |= mask;
617 else
618 *wantSet |= mask;
623 /* If filename part of parameter is * or *.*,
624 * and neither /Q nor /P options were given,
625 * prompt the user whether to proceed.
626 * Returns FALSE if user says no, TRUE otherwise.
627 * *pPrompted is set to TRUE if the user is prompted.
628 * (If /P supplied, del will prompt for individual files later.)
630 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
631 static const WCHAR parmP[] = {'/','P','\0'};
632 static const WCHAR parmQ[] = {'/','Q','\0'};
634 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
635 static const WCHAR anyExt[]= {'.','*','\0'};
636 WCHAR drive[10];
637 WCHAR dir[MAX_PATH];
638 WCHAR fname[MAX_PATH];
639 WCHAR ext[MAX_PATH];
640 WCHAR fpath[MAX_PATH];
642 /* Convert path into actual directory spec */
643 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
644 WCMD_splitpath(fpath, drive, dir, fname, ext);
646 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
647 if ((strcmpW(fname, starW) == 0) &&
648 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
650 WCHAR question[MAXSTRING];
651 static const WCHAR fmt[] = {'%','s',' ','\0'};
653 /* Caller uses this to suppress "file not found" warning later */
654 *pPrompted = TRUE;
656 /* Ask for confirmation */
657 wsprintfW(question, fmt, fpath);
658 return WCMD_ask_confirm(question, TRUE, NULL);
661 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
662 return TRUE;
665 /* Helper function for WCMD_delete().
666 * Deletes a single file, directory, or wildcard.
667 * If /S was given, does it recursively.
668 * Returns TRUE if a file was deleted.
670 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
672 static const WCHAR parmP[] = {'/','P','\0'};
673 static const WCHAR parmS[] = {'/','S','\0'};
674 static const WCHAR parmF[] = {'/','F','\0'};
675 DWORD wanted_attrs;
676 DWORD unwanted_attrs;
677 BOOL found = FALSE;
678 WCHAR argCopy[MAX_PATH];
679 WIN32_FIND_DATAW fd;
680 HANDLE hff;
681 WCHAR fpath[MAX_PATH];
682 WCHAR *p;
683 BOOL handleParm = TRUE;
685 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
687 strcpyW(argCopy, thisArg);
688 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
689 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
691 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
692 /* Skip this arg if user declines to delete *.* */
693 return FALSE;
696 /* First, try to delete in the current directory */
697 hff = FindFirstFileW(argCopy, &fd);
698 if (hff == INVALID_HANDLE_VALUE) {
699 handleParm = FALSE;
700 } else {
701 found = TRUE;
704 /* Support del <dirname> by just deleting all files dirname\* */
705 if (handleParm
706 && (strchrW(argCopy,'*') == NULL)
707 && (strchrW(argCopy,'?') == NULL)
708 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
710 WCHAR modifiedParm[MAX_PATH];
711 static const WCHAR slashStar[] = {'\\','*','\0'};
713 strcpyW(modifiedParm, argCopy);
714 strcatW(modifiedParm, slashStar);
715 FindClose(hff);
716 found = TRUE;
717 WCMD_delete_one(modifiedParm);
719 } else if (handleParm) {
721 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
722 strcpyW (fpath, argCopy);
723 do {
724 p = strrchrW (fpath, '\\');
725 if (p != NULL) {
726 *++p = '\0';
727 strcatW (fpath, fd.cFileName);
729 else strcpyW (fpath, fd.cFileName);
730 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
731 BOOL ok;
733 /* Handle attribute matching (/A) */
734 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
735 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
737 /* /P means prompt for each file */
738 if (ok && strstrW (quals, parmP) != NULL) {
739 WCHAR* question;
741 /* Ask for confirmation */
742 question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
743 ok = WCMD_ask_confirm(question, FALSE, NULL);
744 LocalFree(question);
747 /* Only proceed if ok to */
748 if (ok) {
750 /* If file is read only, and /A:r or /F supplied, delete it */
751 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
752 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
753 strstrW (quals, parmF) != NULL)) {
754 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
757 /* Now do the delete */
758 if (!DeleteFileW(fpath)) WCMD_print_error ();
762 } while (FindNextFileW(hff, &fd) != 0);
763 FindClose (hff);
766 /* Now recurse into all subdirectories handling the parameter in the same way */
767 if (strstrW (quals, parmS) != NULL) {
769 WCHAR thisDir[MAX_PATH];
770 int cPos;
772 WCHAR drive[10];
773 WCHAR dir[MAX_PATH];
774 WCHAR fname[MAX_PATH];
775 WCHAR ext[MAX_PATH];
777 /* Convert path into actual directory spec */
778 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
779 WCMD_splitpath(thisDir, drive, dir, fname, ext);
781 strcpyW(thisDir, drive);
782 strcatW(thisDir, dir);
783 cPos = strlenW(thisDir);
785 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
787 /* Append '*' to the directory */
788 thisDir[cPos] = '*';
789 thisDir[cPos+1] = 0x00;
791 hff = FindFirstFileW(thisDir, &fd);
793 /* Remove residual '*' */
794 thisDir[cPos] = 0x00;
796 if (hff != INVALID_HANDLE_VALUE) {
797 DIRECTORY_STACK *allDirs = NULL;
798 DIRECTORY_STACK *lastEntry = NULL;
800 do {
801 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
802 (strcmpW(fd.cFileName, dotdotW) != 0) &&
803 (strcmpW(fd.cFileName, dotW) != 0)) {
805 DIRECTORY_STACK *nextDir;
806 WCHAR subParm[MAX_PATH];
808 /* Work out search parameter in sub dir */
809 strcpyW (subParm, thisDir);
810 strcatW (subParm, fd.cFileName);
811 strcatW (subParm, slashW);
812 strcatW (subParm, fname);
813 strcatW (subParm, ext);
814 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
816 /* Allocate memory, add to list */
817 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
818 if (allDirs == NULL) allDirs = nextDir;
819 if (lastEntry != NULL) lastEntry->next = nextDir;
820 lastEntry = nextDir;
821 nextDir->next = NULL;
822 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
823 (strlenW(subParm)+1) * sizeof(WCHAR));
824 strcpyW(nextDir->dirName, subParm);
826 } while (FindNextFileW(hff, &fd) != 0);
827 FindClose (hff);
829 /* Go through each subdir doing the delete */
830 while (allDirs != NULL) {
831 DIRECTORY_STACK *tempDir;
833 tempDir = allDirs->next;
834 found |= WCMD_delete_one (allDirs->dirName);
836 HeapFree(GetProcessHeap(),0,allDirs->dirName);
837 HeapFree(GetProcessHeap(),0,allDirs);
838 allDirs = tempDir;
843 return found;
846 /****************************************************************************
847 * WCMD_delete
849 * Delete a file or wildcarded set.
851 * Note on /A:
852 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
853 * - Each set is a pattern, eg /ahr /as-r means
854 * readonly+hidden OR nonreadonly system files
855 * - The '-' applies to a single field, ie /a:-hr means read only
856 * non-hidden files
859 BOOL WCMD_delete (WCHAR *command) {
860 int argno;
861 WCHAR *argN;
862 BOOL argsProcessed = FALSE;
863 BOOL foundAny = FALSE;
865 errorlevel = 0;
867 for (argno=0; ; argno++) {
868 BOOL found;
869 WCHAR *thisArg;
871 argN = NULL;
872 thisArg = WCMD_parameter (command, argno, &argN, NULL);
873 if (!argN)
874 break; /* no more parameters */
875 if (argN[0] == '/')
876 continue; /* skip options */
878 argsProcessed = TRUE;
879 found = WCMD_delete_one(thisArg);
880 if (!found) {
881 errorlevel = 1;
882 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
884 foundAny |= found;
887 /* Handle no valid args */
888 if (!argsProcessed)
889 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
891 return foundAny;
895 * WCMD_strtrim
897 * Returns a trimmed version of s with all leading and trailing whitespace removed
898 * Pre: s non NULL
901 static WCHAR *WCMD_strtrim(const WCHAR *s)
903 DWORD len = strlenW(s);
904 const WCHAR *start = s;
905 WCHAR* result;
907 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
908 return NULL;
910 while (isspaceW(*start)) start++;
911 if (*start) {
912 const WCHAR *end = s + len - 1;
913 while (end > start && isspaceW(*end)) end--;
914 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
915 result[end - start + 1] = '\0';
916 } else {
917 result[0] = '\0';
920 return result;
923 /****************************************************************************
924 * WCMD_echo
926 * Echo input to the screen (or not). We don't try to emulate the bugs
927 * in DOS (try typing "ECHO ON AGAIN" for an example).
930 void WCMD_echo (const WCHAR *command)
932 int count;
933 const WCHAR *origcommand = command;
934 WCHAR *trimmed;
936 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
937 || command[0]==':' || command[0]==';')
938 command++;
940 trimmed = WCMD_strtrim(command);
941 if (!trimmed) return;
943 count = strlenW(trimmed);
944 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
945 && origcommand[0]!=';') {
946 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
947 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
948 return;
951 if (lstrcmpiW(trimmed, onW) == 0)
952 echo_mode = TRUE;
953 else if (lstrcmpiW(trimmed, offW) == 0)
954 echo_mode = FALSE;
955 else {
956 WCMD_output_asis (command);
957 WCMD_output_asis (newlineW);
959 HeapFree(GetProcessHeap(), 0, trimmed);
962 /*****************************************************************************
963 * WCMD_part_execute
965 * Execute a command, and any && or bracketed follow on to the command. The
966 * first command to be executed may not be at the front of the
967 * commands->thiscommand string (eg. it may point after a DO or ELSE)
969 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
970 const WCHAR *variable, const WCHAR *value,
971 BOOL isIF, BOOL executecmds)
973 CMD_LIST *curPosition = *cmdList;
974 int myDepth = (*cmdList)->bracketDepth;
976 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
977 cmdList, wine_dbgstr_w(firstcmd),
978 wine_dbgstr_w(variable), wine_dbgstr_w(value),
979 executecmds);
981 /* Skip leading whitespace between condition and the command */
982 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
984 /* Process the first command, if there is one */
985 if (executecmds && firstcmd && *firstcmd) {
986 WCHAR *command = WCMD_strdupW(firstcmd);
987 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
988 HeapFree(GetProcessHeap(), 0, command);
992 /* If it didn't move the position, step to next command */
993 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
995 /* Process any other parts of the command */
996 if (*cmdList) {
997 BOOL processThese = executecmds;
999 while (*cmdList) {
1000 static const WCHAR ifElse[] = {'e','l','s','e'};
1002 /* execute all appropriate commands */
1003 curPosition = *cmdList;
1005 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1006 *cmdList,
1007 (*cmdList)->prevDelim,
1008 (*cmdList)->bracketDepth, myDepth);
1010 /* Execute any statements appended to the line */
1011 /* FIXME: Only if previous call worked for && or failed for || */
1012 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1013 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1014 if (processThese && (*cmdList)->command) {
1015 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1016 value, cmdList);
1018 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1020 /* Execute any appended to the statement with (...) */
1021 } else if ((*cmdList)->bracketDepth > myDepth) {
1022 if (processThese) {
1023 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1024 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1026 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1028 /* End of the command - does 'ELSE ' follow as the next command? */
1029 } else {
1030 if (isIF
1031 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1032 (*cmdList)->command)) {
1034 /* Swap between if and else processing */
1035 processThese = !processThese;
1037 /* Process the ELSE part */
1038 if (processThese) {
1039 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1040 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1042 /* Skip leading whitespace between condition and the command */
1043 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1044 if (*cmd) {
1045 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1048 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1049 } else {
1050 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1051 break;
1056 return;
1059 /**************************************************************************
1060 * WCMD_for
1062 * Batch file loop processing.
1064 * On entry: cmdList contains the syntax up to the set
1065 * next cmdList and all in that bracket contain the set data
1066 * next cmdlist contains the DO cmd
1067 * following that is either brackets or && entries (as per if)
1071 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1073 WIN32_FIND_DATAW fd;
1074 HANDLE hff;
1075 int i;
1076 static const WCHAR inW[] = {'i','n'};
1077 static const WCHAR doW[] = {'d','o'};
1078 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1079 WCHAR variable[4];
1080 WCHAR *firstCmd;
1081 int thisDepth;
1083 WCHAR *curPos = p;
1084 BOOL expandDirs = FALSE;
1085 BOOL useNumbers = FALSE;
1086 BOOL doFileset = FALSE;
1087 BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
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 doExecuted = TRUE;
1238 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1239 fd.cFileName, FALSE, TRUE);
1242 } while (FindNextFileW(hff, &fd) != 0);
1243 FindClose (hff);
1245 } else {
1246 doExecuted = TRUE;
1247 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1250 } else if (useNumbers) {
1251 /* Convert the first 3 numbers to signed longs and save */
1252 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1253 /* else ignore them! */
1255 /* Filesets - either a list of files, or a command to run and parse the output */
1256 } else if (doFileset && *itemStart != '"') {
1258 HANDLE input;
1259 WCHAR temp_file[MAX_PATH];
1261 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1262 wine_dbgstr_w(item));
1264 /* If backquote or single quote, we need to launch that command
1265 and parse the results - use a temporary file */
1266 if (*itemStart == '`' || *itemStart == '\'') {
1268 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1269 static const WCHAR redirOut[] = {'>','%','s','\0'};
1270 static const WCHAR cmdW[] = {'C','M','D','\0'};
1272 /* Remove trailing character */
1273 itemStart[strlenW(itemStart)-1] = 0x00;
1275 /* Get temp filename */
1276 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1277 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1279 /* Execute program and redirect output */
1280 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1281 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1283 /* Open the file, read line by line and process */
1284 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1285 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1286 } else {
1288 /* Open the file, read line by line and process */
1289 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1290 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1293 /* Process the input file */
1294 if (input == INVALID_HANDLE_VALUE) {
1295 WCMD_print_error ();
1296 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1297 errorlevel = 1;
1298 return; /* FOR loop aborts at first failure here */
1300 } else {
1302 WCHAR buffer[MAXSTRING] = {'\0'};
1303 WCHAR *where, *parm;
1305 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1307 /* Skip blank lines*/
1308 parm = WCMD_parameter (buffer, 0, &where, NULL);
1309 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1310 wine_dbgstr_w(buffer));
1312 if (where) {
1313 /* FIXME: The following should be moved into its own routine and
1314 reused for the string literal parsing below */
1315 thisCmdStart = cmdStart;
1316 doExecuted = TRUE;
1317 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1318 cmdEnd = thisCmdStart;
1321 buffer[0] = 0x00;
1324 CloseHandle (input);
1327 /* Delete the temporary file */
1328 if (*itemStart == '`' || *itemStart == '\'') {
1329 DeleteFileW(temp_file);
1332 /* Filesets - A string literal */
1333 } else if (doFileset && *itemStart == '"') {
1334 WCHAR buffer[MAXSTRING] = {'\0'};
1335 WCHAR *where, *parm;
1337 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1338 strcpyW(buffer, item);
1339 parm = WCMD_parameter (buffer, 0, &where, NULL);
1340 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1341 wine_dbgstr_w(buffer));
1343 if (where) {
1344 /* FIXME: The following should be moved into its own routine and
1345 reused for the string literal parsing below */
1346 thisCmdStart = cmdStart;
1347 doExecuted = TRUE;
1348 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1349 cmdEnd = thisCmdStart;
1353 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1354 cmdEnd = thisCmdStart;
1355 i++;
1358 /* Move onto the next set line */
1359 thisSet = thisSet->nextcommand;
1362 /* If /L is provided, now run the for loop */
1363 if (useNumbers) {
1364 WCHAR thisNum[20];
1365 static const WCHAR fmt[] = {'%','d','\0'};
1367 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1368 numbers[0], numbers[2], numbers[1]);
1369 for (i=numbers[0];
1370 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
1371 i=i + numbers[1]) {
1373 sprintfW(thisNum, fmt, i);
1374 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1376 thisCmdStart = cmdStart;
1377 doExecuted = TRUE;
1378 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1380 cmdEnd = thisCmdStart;
1383 /* Now skip over the do part if we did not perform the for loop so far.
1384 We store in cmdEnd the next command after the do block, but we only
1385 know this if something was run. If it has not been, we need to calculate
1386 it. */
1387 if (!doExecuted) {
1388 thisCmdStart = cmdStart;
1389 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
1390 WCMD_part_execute(&thisCmdStart, firstCmd, NULL, NULL, FALSE, FALSE);
1391 cmdEnd = thisCmdStart;
1394 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1395 all processing, OR it should be pointing to the end of && processing OR
1396 it should be pointing at the NULL end of bracket for the DO. The return
1397 value needs to be the NEXT command to execute, which it either is, or
1398 we need to step over the closing bracket */
1399 *cmdList = cmdEnd;
1400 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1403 /**************************************************************************
1404 * WCMD_give_help
1406 * Simple on-line help. Help text is stored in the resource file.
1409 void WCMD_give_help (const WCHAR *command)
1411 size_t i;
1413 command = WCMD_skip_leading_spaces((WCHAR*) command);
1414 if (strlenW(command) == 0) {
1415 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1417 else {
1418 /* Display help message for builtin commands */
1419 for (i=0; i<=WCMD_EXIT; i++) {
1420 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1421 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1422 WCMD_output_asis (WCMD_LoadMessage(i));
1423 return;
1426 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1427 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1428 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1429 command, -1, externals[i], -1) == CSTR_EQUAL) {
1430 WCHAR cmd[128];
1431 static const WCHAR helpW[] = {' ', '/','?','\0'};
1432 strcpyW(cmd, command);
1433 strcatW(cmd, helpW);
1434 WCMD_run_program(cmd, FALSE);
1435 return;
1438 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1440 return;
1443 /****************************************************************************
1444 * WCMD_go_to
1446 * Batch file jump instruction. Not the most efficient algorithm ;-)
1447 * Prints error message if the specified label cannot be found - the file pointer is
1448 * then at EOF, effectively stopping the batch file.
1449 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1452 void WCMD_goto (CMD_LIST **cmdList) {
1454 WCHAR string[MAX_PATH];
1455 WCHAR current[MAX_PATH];
1457 /* Do not process any more parts of a processed multipart or multilines command */
1458 if (cmdList) *cmdList = NULL;
1460 if (context != NULL) {
1461 WCHAR *paramStart = param1, *str;
1462 static const WCHAR eofW[] = {':','e','o','f','\0'};
1464 if (param1[0] == 0x00) {
1465 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1466 return;
1469 /* Handle special :EOF label */
1470 if (lstrcmpiW (eofW, param1) == 0) {
1471 context -> skip_rest = TRUE;
1472 return;
1475 /* Support goto :label as well as goto label */
1476 if (*paramStart == ':') paramStart++;
1478 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1479 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1480 str = string;
1481 while (isspaceW (*str)) str++;
1482 if (*str == ':') {
1483 DWORD index = 0;
1484 str++;
1485 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1486 index++;
1488 /* ignore space at the end */
1489 current[index] = 0;
1490 if (lstrcmpiW (current, paramStart) == 0) return;
1493 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1495 return;
1498 /*****************************************************************************
1499 * WCMD_pushd
1501 * Push a directory onto the stack
1504 void WCMD_pushd (const WCHAR *command)
1506 struct env_stack *curdir;
1507 WCHAR *thisdir;
1508 static const WCHAR parmD[] = {'/','D','\0'};
1510 if (strchrW(command, '/') != NULL) {
1511 SetLastError(ERROR_INVALID_PARAMETER);
1512 WCMD_print_error();
1513 return;
1516 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1517 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1518 if( !curdir || !thisdir ) {
1519 LocalFree(curdir);
1520 LocalFree(thisdir);
1521 WINE_ERR ("out of memory\n");
1522 return;
1525 /* Change directory using CD code with /D parameter */
1526 strcpyW(quals, parmD);
1527 GetCurrentDirectoryW (1024, thisdir);
1528 errorlevel = 0;
1529 WCMD_setshow_default(command);
1530 if (errorlevel) {
1531 LocalFree(curdir);
1532 LocalFree(thisdir);
1533 return;
1534 } else {
1535 curdir -> next = pushd_directories;
1536 curdir -> strings = thisdir;
1537 if (pushd_directories == NULL) {
1538 curdir -> u.stackdepth = 1;
1539 } else {
1540 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1542 pushd_directories = curdir;
1547 /*****************************************************************************
1548 * WCMD_popd
1550 * Pop a directory from the stack
1553 void WCMD_popd (void) {
1554 struct env_stack *temp = pushd_directories;
1556 if (!pushd_directories)
1557 return;
1559 /* pop the old environment from the stack, and make it the current dir */
1560 pushd_directories = temp->next;
1561 SetCurrentDirectoryW(temp->strings);
1562 LocalFree (temp->strings);
1563 LocalFree (temp);
1566 /****************************************************************************
1567 * WCMD_if
1569 * Batch file conditional.
1571 * On entry, cmdlist will point to command containing the IF, and optionally
1572 * the first command to execute (if brackets not found)
1573 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1574 * If ('s were found, execute all within that bracket
1575 * Command may optionally be followed by an ELSE - need to skip instructions
1576 * in the else using the same logic
1578 * FIXME: Much more syntax checking needed!
1581 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1583 int negate; /* Negate condition */
1584 int test; /* Condition evaluation result */
1585 WCHAR condition[MAX_PATH], *command, *s;
1586 static const WCHAR notW[] = {'n','o','t','\0'};
1587 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1588 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1589 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1590 static const WCHAR eqeqW[] = {'=','=','\0'};
1591 static const WCHAR parmI[] = {'/','I','\0'};
1592 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1594 negate = !lstrcmpiW(param1,notW);
1595 strcpyW(condition, (negate ? param2 : param1));
1596 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1598 if (!lstrcmpiW (condition, errlvlW)) {
1599 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL);
1600 WCHAR *endptr;
1601 long int param_int = strtolW(param, &endptr, 10);
1602 if (*endptr) {
1603 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1604 return;
1606 test = ((long int)errorlevel >= param_int);
1607 WCMD_parameter(p, 2+negate, &command, NULL);
1609 else if (!lstrcmpiW (condition, existW)) {
1610 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1611 WCMD_parameter(p, 2+negate, &command, NULL);
1613 else if (!lstrcmpiW (condition, defdW)) {
1614 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1615 WCMD_parameter(p, 2+negate, &command, NULL);
1617 else if ((s = strstrW (p, eqeqW))) {
1618 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1619 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1620 s += 2;
1621 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1622 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1623 test = caseInsensitive
1624 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1625 leftPart, leftPartEnd-leftPart+1,
1626 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1627 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1628 leftPart, leftPartEnd-leftPart+1,
1629 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1630 WCMD_parameter(s, 1, &command, NULL);
1632 else {
1633 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1634 return;
1637 /* Process rest of IF statement which is on the same line
1638 Note: This may process all or some of the cmdList (eg a GOTO) */
1639 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1642 /****************************************************************************
1643 * WCMD_move
1645 * Move a file, directory tree or wildcarded set of files.
1648 void WCMD_move (void)
1650 int status;
1651 WIN32_FIND_DATAW fd;
1652 HANDLE hff;
1653 WCHAR input[MAX_PATH];
1654 WCHAR output[MAX_PATH];
1655 WCHAR drive[10];
1656 WCHAR dir[MAX_PATH];
1657 WCHAR fname[MAX_PATH];
1658 WCHAR ext[MAX_PATH];
1660 if (param1[0] == 0x00) {
1661 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1662 return;
1665 /* If no destination supplied, assume current directory */
1666 if (param2[0] == 0x00) {
1667 strcpyW(param2, dotW);
1670 /* If 2nd parm is directory, then use original filename */
1671 /* Convert partial path to full path */
1672 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1673 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1674 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1675 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1677 /* Split into components */
1678 WCMD_splitpath(input, drive, dir, fname, ext);
1680 hff = FindFirstFileW(input, &fd);
1681 if (hff == INVALID_HANDLE_VALUE)
1682 return;
1684 do {
1685 WCHAR dest[MAX_PATH];
1686 WCHAR src[MAX_PATH];
1687 DWORD attribs;
1688 BOOL ok = TRUE;
1690 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1692 /* Build src & dest name */
1693 strcpyW(src, drive);
1694 strcatW(src, dir);
1696 /* See if dest is an existing directory */
1697 attribs = GetFileAttributesW(output);
1698 if (attribs != INVALID_FILE_ATTRIBUTES &&
1699 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1700 strcpyW(dest, output);
1701 strcatW(dest, slashW);
1702 strcatW(dest, fd.cFileName);
1703 } else {
1704 strcpyW(dest, output);
1707 strcatW(src, fd.cFileName);
1709 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1710 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1712 /* If destination exists, prompt unless /Y supplied */
1713 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1714 BOOL force = FALSE;
1715 WCHAR copycmd[MAXSTRING];
1716 DWORD len;
1718 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1719 if (strstrW (quals, parmNoY))
1720 force = FALSE;
1721 else if (strstrW (quals, parmY))
1722 force = TRUE;
1723 else {
1724 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1725 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1726 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1727 && ! lstrcmpiW (copycmd, parmY));
1730 /* Prompt if overwriting */
1731 if (!force) {
1732 WCHAR* question;
1734 /* Ask for confirmation */
1735 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1736 ok = WCMD_ask_confirm(question, FALSE, NULL);
1737 LocalFree(question);
1739 /* So delete the destination prior to the move */
1740 if (ok) {
1741 if (!DeleteFileW(dest)) {
1742 WCMD_print_error ();
1743 errorlevel = 1;
1744 ok = FALSE;
1750 if (ok) {
1751 status = MoveFileW(src, dest);
1752 } else {
1753 status = 1; /* Anything other than 0 to prevent error msg below */
1756 if (!status) {
1757 WCMD_print_error ();
1758 errorlevel = 1;
1760 } while (FindNextFileW(hff, &fd) != 0);
1762 FindClose(hff);
1765 /****************************************************************************
1766 * WCMD_pause
1768 * Suspend execution of a batch script until a key is typed
1771 void WCMD_pause (void)
1773 DWORD oldmode;
1774 BOOL have_console;
1775 DWORD count;
1776 WCHAR key;
1777 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1779 have_console = GetConsoleMode(hIn, &oldmode);
1780 if (have_console)
1781 SetConsoleMode(hIn, 0);
1783 WCMD_output_asis(anykey);
1784 WCMD_ReadFile(hIn, &key, 1, &count);
1785 if (have_console)
1786 SetConsoleMode(hIn, oldmode);
1789 /****************************************************************************
1790 * WCMD_remove_dir
1792 * Delete a directory.
1795 void WCMD_remove_dir (WCHAR *command) {
1797 int argno = 0;
1798 int argsProcessed = 0;
1799 WCHAR *argN = command;
1800 static const WCHAR parmS[] = {'/','S','\0'};
1801 static const WCHAR parmQ[] = {'/','Q','\0'};
1803 /* Loop through all args */
1804 while (argN) {
1805 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1806 if (argN && argN[0] != '/') {
1807 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1808 wine_dbgstr_w(quals));
1809 argsProcessed++;
1811 /* If subdirectory search not supplied, just try to remove
1812 and report error if it fails (eg if it contains a file) */
1813 if (strstrW (quals, parmS) == NULL) {
1814 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1816 /* Otherwise use ShFileOp to recursively remove a directory */
1817 } else {
1819 SHFILEOPSTRUCTW lpDir;
1821 /* Ask first */
1822 if (strstrW (quals, parmQ) == NULL) {
1823 BOOL ok;
1824 WCHAR question[MAXSTRING];
1825 static const WCHAR fmt[] = {'%','s',' ','\0'};
1827 /* Ask for confirmation */
1828 wsprintfW(question, fmt, thisArg);
1829 ok = WCMD_ask_confirm(question, TRUE, NULL);
1831 /* Abort if answer is 'N' */
1832 if (!ok) return;
1835 /* Do the delete */
1836 lpDir.hwnd = NULL;
1837 lpDir.pTo = NULL;
1838 lpDir.pFrom = thisArg;
1839 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1840 lpDir.wFunc = FO_DELETE;
1842 /* SHFileOperationW needs file list with a double null termination */
1843 thisArg[lstrlenW(thisArg) + 1] = 0x00;
1845 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1850 /* Handle no valid args */
1851 if (argsProcessed == 0) {
1852 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1853 return;
1858 /****************************************************************************
1859 * WCMD_rename
1861 * Rename a file.
1864 void WCMD_rename (void)
1866 int status;
1867 HANDLE hff;
1868 WIN32_FIND_DATAW fd;
1869 WCHAR input[MAX_PATH];
1870 WCHAR *dotDst = NULL;
1871 WCHAR drive[10];
1872 WCHAR dir[MAX_PATH];
1873 WCHAR fname[MAX_PATH];
1874 WCHAR ext[MAX_PATH];
1876 errorlevel = 0;
1878 /* Must be at least two args */
1879 if (param1[0] == 0x00 || param2[0] == 0x00) {
1880 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1881 errorlevel = 1;
1882 return;
1885 /* Destination cannot contain a drive letter or directory separator */
1886 if ((strchrW(param2,':') != NULL) || (strchrW(param2,'\\') != NULL)) {
1887 SetLastError(ERROR_INVALID_PARAMETER);
1888 WCMD_print_error();
1889 errorlevel = 1;
1890 return;
1893 /* Convert partial path to full path */
1894 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1895 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1896 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1897 dotDst = strchrW(param2, '.');
1899 /* Split into components */
1900 WCMD_splitpath(input, drive, dir, fname, ext);
1902 hff = FindFirstFileW(input, &fd);
1903 if (hff == INVALID_HANDLE_VALUE)
1904 return;
1906 do {
1907 WCHAR dest[MAX_PATH];
1908 WCHAR src[MAX_PATH];
1909 WCHAR *dotSrc = NULL;
1910 int dirLen;
1912 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1914 /* FIXME: If dest name or extension is *, replace with filename/ext
1915 part otherwise use supplied name. This supports:
1916 ren *.fred *.jim
1917 ren jim.* fred.* etc
1918 However, windows has a more complex algorithm supporting eg
1919 ?'s and *'s mid name */
1920 dotSrc = strchrW(fd.cFileName, '.');
1922 /* Build src & dest name */
1923 strcpyW(src, drive);
1924 strcatW(src, dir);
1925 strcpyW(dest, src);
1926 dirLen = strlenW(src);
1927 strcatW(src, fd.cFileName);
1929 /* Build name */
1930 if (param2[0] == '*') {
1931 strcatW(dest, fd.cFileName);
1932 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1933 } else {
1934 strcatW(dest, param2);
1935 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1938 /* Build Extension */
1939 if (dotDst && (*(dotDst+1)=='*')) {
1940 if (dotSrc) strcatW(dest, dotSrc);
1941 } else if (dotDst) {
1942 if (dotDst) strcatW(dest, dotDst);
1945 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1946 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1948 status = MoveFileW(src, dest);
1950 if (!status) {
1951 WCMD_print_error ();
1952 errorlevel = 1;
1954 } while (FindNextFileW(hff, &fd) != 0);
1956 FindClose(hff);
1959 /*****************************************************************************
1960 * WCMD_dupenv
1962 * Make a copy of the environment.
1964 static WCHAR *WCMD_dupenv( const WCHAR *env )
1966 WCHAR *env_copy;
1967 int len;
1969 if( !env )
1970 return NULL;
1972 len = 0;
1973 while ( env[len] )
1974 len += (strlenW(&env[len]) + 1);
1976 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1977 if (!env_copy)
1979 WINE_ERR("out of memory\n");
1980 return env_copy;
1982 memcpy (env_copy, env, len*sizeof (WCHAR));
1983 env_copy[len] = 0;
1985 return env_copy;
1988 /*****************************************************************************
1989 * WCMD_setlocal
1991 * setlocal pushes the environment onto a stack
1992 * Save the environment as unicode so we don't screw anything up.
1994 void WCMD_setlocal (const WCHAR *s) {
1995 WCHAR *env;
1996 struct env_stack *env_copy;
1997 WCHAR cwd[MAX_PATH];
1999 /* DISABLEEXTENSIONS ignored */
2001 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
2002 if( !env_copy )
2004 WINE_ERR ("out of memory\n");
2005 return;
2008 env = GetEnvironmentStringsW ();
2010 env_copy->strings = WCMD_dupenv (env);
2011 if (env_copy->strings)
2013 env_copy->next = saved_environment;
2014 saved_environment = env_copy;
2016 /* Save the current drive letter */
2017 GetCurrentDirectoryW(MAX_PATH, cwd);
2018 env_copy->u.cwd = cwd[0];
2020 else
2021 LocalFree (env_copy);
2023 FreeEnvironmentStringsW (env);
2027 /*****************************************************************************
2028 * WCMD_endlocal
2030 * endlocal pops the environment off a stack
2031 * Note: When searching for '=', search from WCHAR position 1, to handle
2032 * special internal environment variables =C:, =D: etc
2034 void WCMD_endlocal (void) {
2035 WCHAR *env, *old, *p;
2036 struct env_stack *temp;
2037 int len, n;
2039 if (!saved_environment)
2040 return;
2042 /* pop the old environment from the stack */
2043 temp = saved_environment;
2044 saved_environment = temp->next;
2046 /* delete the current environment, totally */
2047 env = GetEnvironmentStringsW ();
2048 old = WCMD_dupenv (GetEnvironmentStringsW ());
2049 len = 0;
2050 while (old[len]) {
2051 n = strlenW(&old[len]) + 1;
2052 p = strchrW(&old[len] + 1, '=');
2053 if (p)
2055 *p++ = 0;
2056 SetEnvironmentVariableW (&old[len], NULL);
2058 len += n;
2060 LocalFree (old);
2061 FreeEnvironmentStringsW (env);
2063 /* restore old environment */
2064 env = temp->strings;
2065 len = 0;
2066 while (env[len]) {
2067 n = strlenW(&env[len]) + 1;
2068 p = strchrW(&env[len] + 1, '=');
2069 if (p)
2071 *p++ = 0;
2072 SetEnvironmentVariableW (&env[len], p);
2074 len += n;
2077 /* Restore current drive letter */
2078 if (IsCharAlphaW(temp->u.cwd)) {
2079 WCHAR envvar[4];
2080 WCHAR cwd[MAX_PATH];
2081 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2083 wsprintfW(envvar, fmt, temp->u.cwd);
2084 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2085 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2086 SetCurrentDirectoryW(cwd);
2090 LocalFree (env);
2091 LocalFree (temp);
2094 /*****************************************************************************
2095 * WCMD_setshow_default
2097 * Set/Show the current default directory
2100 void WCMD_setshow_default (const WCHAR *command) {
2102 BOOL status;
2103 WCHAR string[1024];
2104 WCHAR cwd[1024];
2105 WCHAR *pos;
2106 WIN32_FIND_DATAW fd;
2107 HANDLE hff;
2108 static const WCHAR parmD[] = {'/','D','\0'};
2110 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2112 /* Skip /D and trailing whitespace if on the front of the command line */
2113 if (CompareStringW(LOCALE_USER_DEFAULT,
2114 NORM_IGNORECASE | SORT_STRINGSORT,
2115 command, 2, parmD, -1) == CSTR_EQUAL) {
2116 command += 2;
2117 while (*command && (*command==' ' || *command=='\t'))
2118 command++;
2121 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2122 if (strlenW(command) == 0) {
2123 strcatW (cwd, newlineW);
2124 WCMD_output_asis (cwd);
2126 else {
2127 /* Remove any double quotes, which may be in the
2128 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2129 pos = string;
2130 while (*command) {
2131 if (*command != '"') *pos++ = *command;
2132 command++;
2134 while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2135 pos--;
2136 *pos = 0x00;
2138 /* Search for appropriate directory */
2139 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2140 hff = FindFirstFileW(string, &fd);
2141 if (hff != INVALID_HANDLE_VALUE) {
2142 do {
2143 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2144 WCHAR fpath[MAX_PATH];
2145 WCHAR drive[10];
2146 WCHAR dir[MAX_PATH];
2147 WCHAR fname[MAX_PATH];
2148 WCHAR ext[MAX_PATH];
2149 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2151 /* Convert path into actual directory spec */
2152 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2153 WCMD_splitpath(fpath, drive, dir, fname, ext);
2155 /* Rebuild path */
2156 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2157 break;
2159 } while (FindNextFileW(hff, &fd) != 0);
2160 FindClose(hff);
2163 /* Change to that directory */
2164 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2166 status = SetCurrentDirectoryW(string);
2167 if (!status) {
2168 errorlevel = 1;
2169 WCMD_print_error ();
2170 return;
2171 } else {
2173 /* Save away the actual new directory, to store as current location */
2174 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2176 /* Restore old directory if drive letter would change, and
2177 CD x:\directory /D (or pushd c:\directory) not supplied */
2178 if ((strstrW(quals, parmD) == NULL) &&
2179 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2180 SetCurrentDirectoryW(cwd);
2184 /* Set special =C: type environment variable, for drive letter of
2185 change of directory, even if path was restored due to missing
2186 /D (allows changing drive letter when not resident on that
2187 drive */
2188 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2189 WCHAR env[4];
2190 strcpyW(env, equalW);
2191 memcpy(env+1, string, 2 * sizeof(WCHAR));
2192 env[3] = 0x00;
2193 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2194 SetEnvironmentVariableW(env, string);
2198 return;
2201 /****************************************************************************
2202 * WCMD_setshow_date
2204 * Set/Show the system date
2205 * FIXME: Can't change date yet
2208 void WCMD_setshow_date (void) {
2210 WCHAR curdate[64], buffer[64];
2211 DWORD count;
2212 static const WCHAR parmT[] = {'/','T','\0'};
2214 if (strlenW(param1) == 0) {
2215 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2216 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2217 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2218 if (strstrW (quals, parmT) == NULL) {
2219 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2220 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2221 if (count > 2) {
2222 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2226 else WCMD_print_error ();
2228 else {
2229 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2233 /****************************************************************************
2234 * WCMD_compare
2236 static int WCMD_compare( const void *a, const void *b )
2238 int r;
2239 const WCHAR * const *str_a = a, * const *str_b = b;
2240 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2241 *str_a, -1, *str_b, -1 );
2242 if( r == CSTR_LESS_THAN ) return -1;
2243 if( r == CSTR_GREATER_THAN ) return 1;
2244 return 0;
2247 /****************************************************************************
2248 * WCMD_setshow_sortenv
2250 * sort variables into order for display
2251 * Optionally only display those who start with a stub
2252 * returns the count displayed
2254 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2256 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2257 const WCHAR **str;
2259 if (stub) stublen = strlenW(stub);
2261 /* count the number of strings, and the total length */
2262 while ( s[len] ) {
2263 len += (strlenW(&s[len]) + 1);
2264 count++;
2267 /* add the strings to an array */
2268 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2269 if( !str )
2270 return 0;
2271 str[0] = s;
2272 for( i=1; i<count; i++ )
2273 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2275 /* sort the array */
2276 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2278 /* print it */
2279 for( i=0; i<count; i++ ) {
2280 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2281 NORM_IGNORECASE | SORT_STRINGSORT,
2282 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2283 /* Don't display special internal variables */
2284 if (str[i][0] != '=') {
2285 WCMD_output_asis(str[i]);
2286 WCMD_output_asis(newlineW);
2287 displayedcount++;
2292 LocalFree( str );
2293 return displayedcount;
2296 /****************************************************************************
2297 * WCMD_setshow_env
2299 * Set/Show the environment variables
2302 void WCMD_setshow_env (WCHAR *s) {
2304 LPVOID env;
2305 WCHAR *p;
2306 int status;
2307 static const WCHAR parmP[] = {'/','P','\0'};
2309 if (param1[0] == 0x00 && quals[0] == 0x00) {
2310 env = GetEnvironmentStringsW();
2311 WCMD_setshow_sortenv( env, NULL );
2312 return;
2315 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2316 if (CompareStringW(LOCALE_USER_DEFAULT,
2317 NORM_IGNORECASE | SORT_STRINGSORT,
2318 s, 2, parmP, -1) == CSTR_EQUAL) {
2319 WCHAR string[MAXSTRING];
2320 DWORD count;
2322 s += 2;
2323 while (*s && (*s==' ' || *s=='\t')) s++;
2324 if (*s=='\"')
2325 WCMD_strip_quotes(s);
2327 /* If no parameter, or no '=' sign, return an error */
2328 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2329 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2330 return;
2333 /* Output the prompt */
2334 *p++ = '\0';
2335 if (strlenW(p) != 0) WCMD_output_asis(p);
2337 /* Read the reply */
2338 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2339 if (count > 1) {
2340 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2341 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2342 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2343 wine_dbgstr_w(string));
2344 status = SetEnvironmentVariableW(s, string);
2347 } else {
2348 DWORD gle;
2350 if (*s=='\"')
2351 WCMD_strip_quotes(s);
2352 p = strchrW (s, '=');
2353 if (p == NULL) {
2354 env = GetEnvironmentStringsW();
2355 if (WCMD_setshow_sortenv( env, s ) == 0) {
2356 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2357 errorlevel = 1;
2359 return;
2361 *p++ = '\0';
2363 if (strlenW(p) == 0) p = NULL;
2364 status = SetEnvironmentVariableW(s, p);
2365 gle = GetLastError();
2366 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2367 errorlevel = 1;
2368 } else if ((!status)) WCMD_print_error();
2369 else errorlevel = 0;
2373 /****************************************************************************
2374 * WCMD_setshow_path
2376 * Set/Show the path environment variable
2379 void WCMD_setshow_path (const WCHAR *command) {
2381 WCHAR string[1024];
2382 DWORD status;
2383 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2384 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2386 if (strlenW(param1) == 0 && strlenW(param2) == 0) {
2387 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2388 if (status != 0) {
2389 WCMD_output_asis ( pathEqW);
2390 WCMD_output_asis ( string);
2391 WCMD_output_asis ( newlineW);
2393 else {
2394 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2397 else {
2398 if (*command == '=') command++; /* Skip leading '=' */
2399 status = SetEnvironmentVariableW(pathW, command);
2400 if (!status) WCMD_print_error();
2404 /****************************************************************************
2405 * WCMD_setshow_prompt
2407 * Set or show the command prompt.
2410 void WCMD_setshow_prompt (void) {
2412 WCHAR *s;
2413 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2415 if (strlenW(param1) == 0) {
2416 SetEnvironmentVariableW(promptW, NULL);
2418 else {
2419 s = param1;
2420 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2421 if (strlenW(s) == 0) {
2422 SetEnvironmentVariableW(promptW, NULL);
2424 else SetEnvironmentVariableW(promptW, s);
2428 /****************************************************************************
2429 * WCMD_setshow_time
2431 * Set/Show the system time
2432 * FIXME: Can't change time yet
2435 void WCMD_setshow_time (void) {
2437 WCHAR curtime[64], buffer[64];
2438 DWORD count;
2439 SYSTEMTIME st;
2440 static const WCHAR parmT[] = {'/','T','\0'};
2442 if (strlenW(param1) == 0) {
2443 GetLocalTime(&st);
2444 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2445 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2446 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2447 if (strstrW (quals, parmT) == NULL) {
2448 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2449 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2450 if (count > 2) {
2451 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2455 else WCMD_print_error ();
2457 else {
2458 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2462 /****************************************************************************
2463 * WCMD_shift
2465 * Shift batch parameters.
2466 * Optional /n says where to start shifting (n=0-8)
2469 void WCMD_shift (const WCHAR *command) {
2470 int start;
2472 if (context != NULL) {
2473 WCHAR *pos = strchrW(command, '/');
2474 int i;
2476 if (pos == NULL) {
2477 start = 0;
2478 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2479 start = (*(pos+1) - '0');
2480 } else {
2481 SetLastError(ERROR_INVALID_PARAMETER);
2482 WCMD_print_error();
2483 return;
2486 WINE_TRACE("Shifting variables, starting at %d\n", start);
2487 for (i=start;i<=8;i++) {
2488 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2490 context -> shift_count[9] = context -> shift_count[9] + 1;
2495 /****************************************************************************
2496 * WCMD_start
2498 void WCMD_start(const WCHAR *command)
2500 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
2501 '\\','s','t','a','r','t','.','e','x','e',0};
2502 WCHAR file[MAX_PATH];
2503 WCHAR *cmdline;
2504 STARTUPINFOW st;
2505 PROCESS_INFORMATION pi;
2507 GetWindowsDirectoryW( file, MAX_PATH );
2508 strcatW( file, exeW );
2509 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
2510 strcpyW( cmdline, file );
2511 strcatW( cmdline, spaceW );
2512 strcatW( cmdline, command );
2514 memset( &st, 0, sizeof(STARTUPINFOW) );
2515 st.cb = sizeof(STARTUPINFOW);
2517 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
2519 WaitForSingleObject( pi.hProcess, INFINITE );
2520 GetExitCodeProcess( pi.hProcess, &errorlevel );
2521 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
2522 CloseHandle(pi.hProcess);
2523 CloseHandle(pi.hThread);
2525 else
2527 SetLastError(ERROR_FILE_NOT_FOUND);
2528 WCMD_print_error ();
2529 errorlevel = 9009;
2531 HeapFree( GetProcessHeap(), 0, cmdline );
2534 /****************************************************************************
2535 * WCMD_title
2537 * Set the console title
2539 void WCMD_title (const WCHAR *command) {
2540 SetConsoleTitleW(command);
2543 /****************************************************************************
2544 * WCMD_type
2546 * Copy a file to standard output.
2549 void WCMD_type (WCHAR *command) {
2551 int argno = 0;
2552 WCHAR *argN = command;
2553 BOOL writeHeaders = FALSE;
2555 if (param1[0] == 0x00) {
2556 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2557 return;
2560 if (param2[0] != 0x00) writeHeaders = TRUE;
2562 /* Loop through all args */
2563 errorlevel = 0;
2564 while (argN) {
2565 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2567 HANDLE h;
2568 WCHAR buffer[512];
2569 DWORD count;
2571 if (!argN) break;
2573 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2574 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2575 FILE_ATTRIBUTE_NORMAL, NULL);
2576 if (h == INVALID_HANDLE_VALUE) {
2577 WCMD_print_error ();
2578 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2579 errorlevel = 1;
2580 } else {
2581 if (writeHeaders) {
2582 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2583 WCMD_output(fmt, thisArg);
2585 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2586 if (count == 0) break; /* ReadFile reports success on EOF! */
2587 buffer[count] = 0;
2588 WCMD_output_asis (buffer);
2590 CloseHandle (h);
2595 /****************************************************************************
2596 * WCMD_more
2598 * Output either a file or stdin to screen in pages
2601 void WCMD_more (WCHAR *command) {
2603 int argno = 0;
2604 WCHAR *argN = command;
2605 WCHAR moreStr[100];
2606 WCHAR moreStrPage[100];
2607 WCHAR buffer[512];
2608 DWORD count;
2609 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2610 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2611 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2612 ')',' ','-','-','\n','\0'};
2613 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2615 /* Prefix the NLS more with '-- ', then load the text */
2616 errorlevel = 0;
2617 strcpyW(moreStr, moreStart);
2618 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2619 (sizeof(moreStr)/sizeof(WCHAR))-3);
2621 if (param1[0] == 0x00) {
2623 /* Wine implements pipes via temporary files, and hence stdin is
2624 effectively reading from the file. This means the prompts for
2625 more are satisfied by the next line from the input (file). To
2626 avoid this, ensure stdin is to the console */
2627 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2628 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2629 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2630 FILE_ATTRIBUTE_NORMAL, 0);
2631 WINE_TRACE("No parms - working probably in pipe mode\n");
2632 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2634 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2635 once you get in this bit unless due to a pipe, its going to end badly... */
2636 wsprintfW(moreStrPage, moreFmt, moreStr);
2638 WCMD_enter_paged_mode(moreStrPage);
2639 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2640 if (count == 0) break; /* ReadFile reports success on EOF! */
2641 buffer[count] = 0;
2642 WCMD_output_asis (buffer);
2644 WCMD_leave_paged_mode();
2646 /* Restore stdin to what it was */
2647 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2648 CloseHandle(hConIn);
2650 return;
2651 } else {
2652 BOOL needsPause = FALSE;
2654 /* Loop through all args */
2655 WINE_TRACE("Parms supplied - working through each file\n");
2656 WCMD_enter_paged_mode(moreStrPage);
2658 while (argN) {
2659 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2660 HANDLE h;
2662 if (!argN) break;
2664 if (needsPause) {
2666 /* Wait */
2667 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2668 WCMD_leave_paged_mode();
2669 WCMD_output_asis(moreStrPage);
2670 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2671 WCMD_enter_paged_mode(moreStrPage);
2675 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2676 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2677 FILE_ATTRIBUTE_NORMAL, NULL);
2678 if (h == INVALID_HANDLE_VALUE) {
2679 WCMD_print_error ();
2680 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2681 errorlevel = 1;
2682 } else {
2683 ULONG64 curPos = 0;
2684 ULONG64 fileLen = 0;
2685 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2687 /* Get the file size */
2688 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2689 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2691 needsPause = TRUE;
2692 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2693 if (count == 0) break; /* ReadFile reports success on EOF! */
2694 buffer[count] = 0;
2695 curPos += count;
2697 /* Update % count (would be used in WCMD_output_asis as prompt) */
2698 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2700 WCMD_output_asis (buffer);
2702 CloseHandle (h);
2706 WCMD_leave_paged_mode();
2710 /****************************************************************************
2711 * WCMD_verify
2713 * Display verify flag.
2714 * FIXME: We don't actually do anything with the verify flag other than toggle
2715 * it...
2718 void WCMD_verify (const WCHAR *command) {
2720 int count;
2722 count = strlenW(command);
2723 if (count == 0) {
2724 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2725 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2726 return;
2728 if (lstrcmpiW(command, onW) == 0) {
2729 verify_mode = TRUE;
2730 return;
2732 else if (lstrcmpiW(command, offW) == 0) {
2733 verify_mode = FALSE;
2734 return;
2736 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2739 /****************************************************************************
2740 * WCMD_version
2742 * Display version info.
2745 void WCMD_version (void) {
2747 WCMD_output_asis (version_string);
2751 /****************************************************************************
2752 * WCMD_volume
2754 * Display volume information (set_label = FALSE)
2755 * Additionally set volume label (set_label = TRUE)
2756 * Returns 1 on success, 0 otherwise
2759 int WCMD_volume(BOOL set_label, const WCHAR *path)
2761 DWORD count, serial;
2762 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2763 BOOL status;
2765 if (strlenW(path) == 0) {
2766 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2767 if (!status) {
2768 WCMD_print_error ();
2769 return 0;
2771 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2772 &serial, NULL, NULL, NULL, 0);
2774 else {
2775 static const WCHAR fmt[] = {'%','s','\\','\0'};
2776 if ((path[1] != ':') || (strlenW(path) != 2)) {
2777 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2778 return 0;
2780 wsprintfW (curdir, fmt, path);
2781 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2782 &serial, NULL,
2783 NULL, NULL, 0);
2785 if (!status) {
2786 WCMD_print_error ();
2787 return 0;
2789 if (label[0] != '\0') {
2790 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
2791 curdir[0], label);
2793 else {
2794 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
2795 curdir[0]);
2797 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
2798 HIWORD(serial), LOWORD(serial));
2799 if (set_label) {
2800 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2801 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2802 if (count > 1) {
2803 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2804 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2806 if (strlenW(path) != 0) {
2807 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2809 else {
2810 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2813 return 1;
2816 /**************************************************************************
2817 * WCMD_exit
2819 * Exit either the process, or just this batch program
2823 void WCMD_exit (CMD_LIST **cmdList) {
2825 static const WCHAR parmB[] = {'/','B','\0'};
2826 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2828 if (context && lstrcmpiW(quals, parmB) == 0) {
2829 errorlevel = rc;
2830 context -> skip_rest = TRUE;
2831 *cmdList = NULL;
2832 } else {
2833 ExitProcess(rc);
2838 /*****************************************************************************
2839 * WCMD_assoc
2841 * Lists or sets file associations (assoc = TRUE)
2842 * Lists or sets file types (assoc = FALSE)
2844 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2846 HKEY key;
2847 DWORD accessOptions = KEY_READ;
2848 WCHAR *newValue;
2849 LONG rc = ERROR_SUCCESS;
2850 WCHAR keyValue[MAXSTRING];
2851 DWORD valueLen = MAXSTRING;
2852 HKEY readKey;
2853 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2854 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2856 /* See if parameter includes '=' */
2857 errorlevel = 0;
2858 newValue = strchrW(command, '=');
2859 if (newValue) accessOptions |= KEY_WRITE;
2861 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2862 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2863 accessOptions, &key) != ERROR_SUCCESS) {
2864 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2865 return;
2868 /* If no parameters then list all associations */
2869 if (*command == 0x00) {
2870 int index = 0;
2872 /* Enumerate all the keys */
2873 while (rc != ERROR_NO_MORE_ITEMS) {
2874 WCHAR keyName[MAXSTRING];
2875 DWORD nameLen;
2877 /* Find the next value */
2878 nameLen = MAXSTRING;
2879 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2881 if (rc == ERROR_SUCCESS) {
2883 /* Only interested in extension ones if assoc, or others
2884 if not assoc */
2885 if ((keyName[0] == '.' && assoc) ||
2886 (!(keyName[0] == '.') && (!assoc)))
2888 WCHAR subkey[MAXSTRING];
2889 strcpyW(subkey, keyName);
2890 if (!assoc) strcatW(subkey, shOpCmdW);
2892 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2894 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2895 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2896 WCMD_output_asis(keyName);
2897 WCMD_output_asis(equalW);
2898 /* If no default value found, leave line empty after '=' */
2899 if (rc == ERROR_SUCCESS) {
2900 WCMD_output_asis(keyValue);
2902 WCMD_output_asis(newlineW);
2903 RegCloseKey(readKey);
2909 } else {
2911 /* Parameter supplied - if no '=' on command line, its a query */
2912 if (newValue == NULL) {
2913 WCHAR *space;
2914 WCHAR subkey[MAXSTRING];
2916 /* Query terminates the parameter at the first space */
2917 strcpyW(keyValue, command);
2918 space = strchrW(keyValue, ' ');
2919 if (space) *space=0x00;
2921 /* Set up key name */
2922 strcpyW(subkey, keyValue);
2923 if (!assoc) strcatW(subkey, shOpCmdW);
2925 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2927 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2928 WCMD_output_asis(command);
2929 WCMD_output_asis(equalW);
2930 /* If no default value found, leave line empty after '=' */
2931 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2932 WCMD_output_asis(newlineW);
2933 RegCloseKey(readKey);
2935 } else {
2936 WCHAR msgbuffer[MAXSTRING];
2938 /* Load the translated 'File association not found' */
2939 if (assoc) {
2940 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2941 } else {
2942 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2944 WCMD_output_stderr(msgbuffer, keyValue);
2945 errorlevel = 2;
2948 /* Not a query - its a set or clear of a value */
2949 } else {
2951 WCHAR subkey[MAXSTRING];
2953 /* Get pointer to new value */
2954 *newValue = 0x00;
2955 newValue++;
2957 /* Set up key name */
2958 strcpyW(subkey, command);
2959 if (!assoc) strcatW(subkey, shOpCmdW);
2961 /* If nothing after '=' then clear value - only valid for ASSOC */
2962 if (*newValue == 0x00) {
2964 if (assoc) rc = RegDeleteKeyW(key, command);
2965 if (assoc && rc == ERROR_SUCCESS) {
2966 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2968 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2969 WCMD_print_error();
2970 errorlevel = 2;
2972 } else {
2973 WCHAR msgbuffer[MAXSTRING];
2975 /* Load the translated 'File association not found' */
2976 if (assoc) {
2977 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2978 sizeof(msgbuffer)/sizeof(WCHAR));
2979 } else {
2980 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2981 sizeof(msgbuffer)/sizeof(WCHAR));
2983 WCMD_output_stderr(msgbuffer, keyValue);
2984 errorlevel = 2;
2987 /* It really is a set value = contents */
2988 } else {
2989 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2990 accessOptions, NULL, &readKey, NULL);
2991 if (rc == ERROR_SUCCESS) {
2992 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2993 (LPBYTE)newValue,
2994 sizeof(WCHAR) * (strlenW(newValue) + 1));
2995 RegCloseKey(readKey);
2998 if (rc != ERROR_SUCCESS) {
2999 WCMD_print_error();
3000 errorlevel = 2;
3001 } else {
3002 WCMD_output_asis(command);
3003 WCMD_output_asis(equalW);
3004 WCMD_output_asis(newValue);
3005 WCMD_output_asis(newlineW);
3011 /* Clean up */
3012 RegCloseKey(key);
3015 /****************************************************************************
3016 * WCMD_color
3018 * Colors the terminal screen.
3021 void WCMD_color (void) {
3023 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3024 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3026 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3027 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3028 return;
3031 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3033 COORD topLeft;
3034 DWORD screenSize;
3035 DWORD color = 0;
3037 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3039 topLeft.X = 0;
3040 topLeft.Y = 0;
3042 /* Convert the color hex digits */
3043 if (param1[0] == 0x00) {
3044 color = defaultColor;
3045 } else {
3046 color = strtoulW(param1, NULL, 16);
3049 /* Fail if fg == bg color */
3050 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3051 errorlevel = 1;
3052 return;
3055 /* Set the current screen contents and ensure all future writes
3056 remain this color */
3057 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3058 SetConsoleTextAttribute(hStdOut, color);