cmd: for /l with zero iterations failed to skip its commands.
[wine.git] / programs / cmd / builtins.c
bloba83947b0c3d560cacb069564ad28e1f5afde50bf
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 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1088 int itemNum;
1089 CMD_LIST *thisCmdStart;
1092 /* Handle optional qualifiers (multiple are allowed) */
1093 while (*curPos && *curPos == '/') {
1094 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
1095 curPos++;
1096 switch (toupperW(*curPos)) {
1097 case 'D': curPos++; expandDirs = TRUE; break;
1098 case 'L': curPos++; useNumbers = TRUE; break;
1100 /* Recursive is special case - /R can have an optional path following it */
1101 /* filenamesets are another special case - /F can have an optional options following it */
1102 case 'R':
1103 case 'F':
1105 BOOL isRecursive = (*curPos == 'R');
1107 if (!isRecursive)
1108 doFileset = TRUE;
1110 /* Skip whitespace */
1111 curPos++;
1112 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1114 /* Next parm is either qualifier, path/options or variable -
1115 only care about it if it is the path/options */
1116 if (*curPos && *curPos != '/' && *curPos != '%') {
1117 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
1118 else {
1119 static unsigned int once;
1120 if (!once++) WINE_FIXME("/F needs to handle options\n");
1123 break;
1125 default:
1126 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
1127 curPos++;
1130 /* Skip whitespace between qualifiers */
1131 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1134 /* Skip whitespace before variable */
1135 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1137 /* Ensure line continues with variable */
1138 if (!*curPos || *curPos != '%') {
1139 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1140 return;
1143 /* Variable should follow */
1144 i = 0;
1145 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
1146 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
1147 variable[i] = 0x00;
1148 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1149 curPos = &curPos[i];
1151 /* Skip whitespace before IN */
1152 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1154 /* Ensure line continues with IN */
1155 if (!*curPos
1156 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
1158 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1159 return;
1162 /* Save away where the set of data starts and the variable */
1163 thisDepth = (*cmdList)->bracketDepth;
1164 *cmdList = (*cmdList)->nextcommand;
1165 setStart = (*cmdList);
1167 /* Skip until the close bracket */
1168 WINE_TRACE("Searching %p as the set\n", *cmdList);
1169 while (*cmdList &&
1170 (*cmdList)->command != NULL &&
1171 (*cmdList)->bracketDepth > thisDepth) {
1172 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1173 *cmdList = (*cmdList)->nextcommand;
1176 /* Skip the close bracket, if there is one */
1177 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1179 /* Syntax error if missing close bracket, or nothing following it
1180 and once we have the complete set, we expect a DO */
1181 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1182 if ((*cmdList == NULL)
1183 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1185 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1186 return;
1189 /* Save away the starting position for the commands (and offset for the
1190 first one */
1191 cmdStart = *cmdList;
1192 cmdEnd = *cmdList;
1193 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1194 itemNum = 0;
1196 thisSet = setStart;
1197 /* Loop through all set entries */
1198 while (thisSet &&
1199 thisSet->command != NULL &&
1200 thisSet->bracketDepth >= thisDepth) {
1202 /* Loop through all entries on the same line */
1203 WCHAR *item;
1204 WCHAR *itemStart;
1206 WINE_TRACE("Processing for set %p\n", thisSet);
1207 i = 0;
1208 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1211 * If the parameter within the set has a wildcard then search for matching files
1212 * otherwise do a literal substitution.
1214 static const WCHAR wildcards[] = {'*','?','\0'};
1215 thisCmdStart = cmdStart;
1217 itemNum++;
1218 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1220 if (!useNumbers && !doFileset) {
1221 if (strpbrkW (item, wildcards)) {
1222 hff = FindFirstFileW(item, &fd);
1223 if (hff != INVALID_HANDLE_VALUE) {
1224 do {
1225 BOOL isDirectory = FALSE;
1227 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1229 /* Handle as files or dirs appropriately, but ignore . and .. */
1230 if (isDirectory == expandDirs &&
1231 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1232 (strcmpW(fd.cFileName, dotW) != 0))
1234 thisCmdStart = cmdStart;
1235 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1236 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1237 fd.cFileName, FALSE, TRUE);
1240 } while (FindNextFileW(hff, &fd) != 0);
1241 FindClose (hff);
1243 } else {
1244 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1247 } else if (useNumbers) {
1248 /* Convert the first 3 numbers to signed longs and save */
1249 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1250 /* else ignore them! */
1252 /* Filesets - either a list of files, or a command to run and parse the output */
1253 } else if (doFileset && *itemStart != '"') {
1255 HANDLE input;
1256 WCHAR temp_file[MAX_PATH];
1258 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1259 wine_dbgstr_w(item));
1261 /* If backquote or single quote, we need to launch that command
1262 and parse the results - use a temporary file */
1263 if (*itemStart == '`' || *itemStart == '\'') {
1265 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1266 static const WCHAR redirOut[] = {'>','%','s','\0'};
1267 static const WCHAR cmdW[] = {'C','M','D','\0'};
1269 /* Remove trailing character */
1270 itemStart[strlenW(itemStart)-1] = 0x00;
1272 /* Get temp filename */
1273 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1274 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1276 /* Execute program and redirect output */
1277 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1278 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1280 /* Open the file, read line by line and process */
1281 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1282 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1283 } else {
1285 /* Open the file, read line by line and process */
1286 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1287 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1290 /* Process the input file */
1291 if (input == INVALID_HANDLE_VALUE) {
1292 WCMD_print_error ();
1293 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1294 errorlevel = 1;
1295 return; /* FOR loop aborts at first failure here */
1297 } else {
1299 WCHAR buffer[MAXSTRING] = {'\0'};
1300 WCHAR *where, *parm;
1302 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1304 /* Skip blank lines*/
1305 parm = WCMD_parameter (buffer, 0, &where, NULL);
1306 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1307 wine_dbgstr_w(buffer));
1309 if (where) {
1310 /* FIXME: The following should be moved into its own routine and
1311 reused for the string literal parsing below */
1312 thisCmdStart = cmdStart;
1313 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1314 cmdEnd = thisCmdStart;
1317 buffer[0] = 0x00;
1320 CloseHandle (input);
1323 /* Delete the temporary file */
1324 if (*itemStart == '`' || *itemStart == '\'') {
1325 DeleteFileW(temp_file);
1328 /* Filesets - A string literal */
1329 } else if (doFileset && *itemStart == '"') {
1330 WCHAR buffer[MAXSTRING] = {'\0'};
1331 WCHAR *where, *parm;
1333 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1334 strcpyW(buffer, item);
1335 parm = WCMD_parameter (buffer, 0, &where, NULL);
1336 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1337 wine_dbgstr_w(buffer));
1339 if (where) {
1340 /* FIXME: The following should be moved into its own routine and
1341 reused for the string literal parsing below */
1342 thisCmdStart = cmdStart;
1343 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1344 cmdEnd = thisCmdStart;
1348 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1349 cmdEnd = thisCmdStart;
1350 i++;
1353 /* Move onto the next set line */
1354 thisSet = thisSet->nextcommand;
1357 /* If /L is provided, now run the for loop */
1358 if (useNumbers) {
1359 WCHAR thisNum[20];
1360 static const WCHAR fmt[] = {'%','d','\0'};
1362 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1363 numbers[0], numbers[2], numbers[1]);
1364 for (i=numbers[0];
1365 (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
1366 i=i + numbers[1]) {
1368 sprintfW(thisNum, fmt, i);
1369 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1371 thisCmdStart = cmdStart;
1372 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1375 /* Now skip over the subsequent commands if we did not perform the for loop */
1376 if (thisCmdStart == cmdStart) {
1377 WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
1378 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, FALSE);
1380 cmdEnd = thisCmdStart;
1383 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1384 all processing, OR it should be pointing to the end of && processing OR
1385 it should be pointing at the NULL end of bracket for the DO. The return
1386 value needs to be the NEXT command to execute, which it either is, or
1387 we need to step over the closing bracket */
1388 *cmdList = cmdEnd;
1389 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1392 /**************************************************************************
1393 * WCMD_give_help
1395 * Simple on-line help. Help text is stored in the resource file.
1398 void WCMD_give_help (const WCHAR *command)
1400 size_t i;
1402 command = WCMD_skip_leading_spaces((WCHAR*) command);
1403 if (strlenW(command) == 0) {
1404 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1406 else {
1407 /* Display help message for builtin commands */
1408 for (i=0; i<=WCMD_EXIT; i++) {
1409 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1410 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1411 WCMD_output_asis (WCMD_LoadMessage(i));
1412 return;
1415 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1416 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1417 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1418 command, -1, externals[i], -1) == CSTR_EQUAL) {
1419 WCHAR cmd[128];
1420 static const WCHAR helpW[] = {' ', '/','?','\0'};
1421 strcpyW(cmd, command);
1422 strcatW(cmd, helpW);
1423 WCMD_run_program(cmd, FALSE);
1424 return;
1427 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1429 return;
1432 /****************************************************************************
1433 * WCMD_go_to
1435 * Batch file jump instruction. Not the most efficient algorithm ;-)
1436 * Prints error message if the specified label cannot be found - the file pointer is
1437 * then at EOF, effectively stopping the batch file.
1438 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1441 void WCMD_goto (CMD_LIST **cmdList) {
1443 WCHAR string[MAX_PATH];
1444 WCHAR current[MAX_PATH];
1446 /* Do not process any more parts of a processed multipart or multilines command */
1447 if (cmdList) *cmdList = NULL;
1449 if (context != NULL) {
1450 WCHAR *paramStart = param1, *str;
1451 static const WCHAR eofW[] = {':','e','o','f','\0'};
1453 if (param1[0] == 0x00) {
1454 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1455 return;
1458 /* Handle special :EOF label */
1459 if (lstrcmpiW (eofW, param1) == 0) {
1460 context -> skip_rest = TRUE;
1461 return;
1464 /* Support goto :label as well as goto label */
1465 if (*paramStart == ':') paramStart++;
1467 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1468 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1469 str = string;
1470 while (isspaceW (*str)) str++;
1471 if (*str == ':') {
1472 DWORD index = 0;
1473 str++;
1474 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1475 index++;
1477 /* ignore space at the end */
1478 current[index] = 0;
1479 if (lstrcmpiW (current, paramStart) == 0) return;
1482 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1484 return;
1487 /*****************************************************************************
1488 * WCMD_pushd
1490 * Push a directory onto the stack
1493 void WCMD_pushd (const WCHAR *command)
1495 struct env_stack *curdir;
1496 WCHAR *thisdir;
1497 static const WCHAR parmD[] = {'/','D','\0'};
1499 if (strchrW(command, '/') != NULL) {
1500 SetLastError(ERROR_INVALID_PARAMETER);
1501 WCMD_print_error();
1502 return;
1505 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1506 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1507 if( !curdir || !thisdir ) {
1508 LocalFree(curdir);
1509 LocalFree(thisdir);
1510 WINE_ERR ("out of memory\n");
1511 return;
1514 /* Change directory using CD code with /D parameter */
1515 strcpyW(quals, parmD);
1516 GetCurrentDirectoryW (1024, thisdir);
1517 errorlevel = 0;
1518 WCMD_setshow_default(command);
1519 if (errorlevel) {
1520 LocalFree(curdir);
1521 LocalFree(thisdir);
1522 return;
1523 } else {
1524 curdir -> next = pushd_directories;
1525 curdir -> strings = thisdir;
1526 if (pushd_directories == NULL) {
1527 curdir -> u.stackdepth = 1;
1528 } else {
1529 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1531 pushd_directories = curdir;
1536 /*****************************************************************************
1537 * WCMD_popd
1539 * Pop a directory from the stack
1542 void WCMD_popd (void) {
1543 struct env_stack *temp = pushd_directories;
1545 if (!pushd_directories)
1546 return;
1548 /* pop the old environment from the stack, and make it the current dir */
1549 pushd_directories = temp->next;
1550 SetCurrentDirectoryW(temp->strings);
1551 LocalFree (temp->strings);
1552 LocalFree (temp);
1555 /****************************************************************************
1556 * WCMD_if
1558 * Batch file conditional.
1560 * On entry, cmdlist will point to command containing the IF, and optionally
1561 * the first command to execute (if brackets not found)
1562 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1563 * If ('s were found, execute all within that bracket
1564 * Command may optionally be followed by an ELSE - need to skip instructions
1565 * in the else using the same logic
1567 * FIXME: Much more syntax checking needed!
1570 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1572 int negate; /* Negate condition */
1573 int test; /* Condition evaluation result */
1574 WCHAR condition[MAX_PATH], *command, *s;
1575 static const WCHAR notW[] = {'n','o','t','\0'};
1576 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1577 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1578 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1579 static const WCHAR eqeqW[] = {'=','=','\0'};
1580 static const WCHAR parmI[] = {'/','I','\0'};
1581 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1583 negate = !lstrcmpiW(param1,notW);
1584 strcpyW(condition, (negate ? param2 : param1));
1585 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1587 if (!lstrcmpiW (condition, errlvlW)) {
1588 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL);
1589 WCHAR *endptr;
1590 long int param_int = strtolW(param, &endptr, 10);
1591 if (*endptr) {
1592 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1593 return;
1595 test = ((long int)errorlevel >= param_int);
1596 WCMD_parameter(p, 2+negate, &command, NULL);
1598 else if (!lstrcmpiW (condition, existW)) {
1599 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1600 WCMD_parameter(p, 2+negate, &command, NULL);
1602 else if (!lstrcmpiW (condition, defdW)) {
1603 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1604 WCMD_parameter(p, 2+negate, &command, NULL);
1606 else if ((s = strstrW (p, eqeqW))) {
1607 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1608 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1609 s += 2;
1610 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1611 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1612 test = caseInsensitive
1613 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1614 leftPart, leftPartEnd-leftPart+1,
1615 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1616 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1617 leftPart, leftPartEnd-leftPart+1,
1618 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1619 WCMD_parameter(s, 1, &command, NULL);
1621 else {
1622 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1623 return;
1626 /* Process rest of IF statement which is on the same line
1627 Note: This may process all or some of the cmdList (eg a GOTO) */
1628 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1631 /****************************************************************************
1632 * WCMD_move
1634 * Move a file, directory tree or wildcarded set of files.
1637 void WCMD_move (void)
1639 int status;
1640 WIN32_FIND_DATAW fd;
1641 HANDLE hff;
1642 WCHAR input[MAX_PATH];
1643 WCHAR output[MAX_PATH];
1644 WCHAR drive[10];
1645 WCHAR dir[MAX_PATH];
1646 WCHAR fname[MAX_PATH];
1647 WCHAR ext[MAX_PATH];
1649 if (param1[0] == 0x00) {
1650 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1651 return;
1654 /* If no destination supplied, assume current directory */
1655 if (param2[0] == 0x00) {
1656 strcpyW(param2, dotW);
1659 /* If 2nd parm is directory, then use original filename */
1660 /* Convert partial path to full path */
1661 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1662 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1663 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1664 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1666 /* Split into components */
1667 WCMD_splitpath(input, drive, dir, fname, ext);
1669 hff = FindFirstFileW(input, &fd);
1670 if (hff == INVALID_HANDLE_VALUE)
1671 return;
1673 do {
1674 WCHAR dest[MAX_PATH];
1675 WCHAR src[MAX_PATH];
1676 DWORD attribs;
1677 BOOL ok = TRUE;
1679 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1681 /* Build src & dest name */
1682 strcpyW(src, drive);
1683 strcatW(src, dir);
1685 /* See if dest is an existing directory */
1686 attribs = GetFileAttributesW(output);
1687 if (attribs != INVALID_FILE_ATTRIBUTES &&
1688 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1689 strcpyW(dest, output);
1690 strcatW(dest, slashW);
1691 strcatW(dest, fd.cFileName);
1692 } else {
1693 strcpyW(dest, output);
1696 strcatW(src, fd.cFileName);
1698 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1699 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1701 /* If destination exists, prompt unless /Y supplied */
1702 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1703 BOOL force = FALSE;
1704 WCHAR copycmd[MAXSTRING];
1705 DWORD len;
1707 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1708 if (strstrW (quals, parmNoY))
1709 force = FALSE;
1710 else if (strstrW (quals, parmY))
1711 force = TRUE;
1712 else {
1713 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1714 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1715 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1716 && ! lstrcmpiW (copycmd, parmY));
1719 /* Prompt if overwriting */
1720 if (!force) {
1721 WCHAR* question;
1723 /* Ask for confirmation */
1724 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1725 ok = WCMD_ask_confirm(question, FALSE, NULL);
1726 LocalFree(question);
1728 /* So delete the destination prior to the move */
1729 if (ok) {
1730 if (!DeleteFileW(dest)) {
1731 WCMD_print_error ();
1732 errorlevel = 1;
1733 ok = FALSE;
1739 if (ok) {
1740 status = MoveFileW(src, dest);
1741 } else {
1742 status = 1; /* Anything other than 0 to prevent error msg below */
1745 if (!status) {
1746 WCMD_print_error ();
1747 errorlevel = 1;
1749 } while (FindNextFileW(hff, &fd) != 0);
1751 FindClose(hff);
1754 /****************************************************************************
1755 * WCMD_pause
1757 * Suspend execution of a batch script until a key is typed
1760 void WCMD_pause (void)
1762 DWORD oldmode;
1763 BOOL have_console;
1764 DWORD count;
1765 WCHAR key;
1766 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1768 have_console = GetConsoleMode(hIn, &oldmode);
1769 if (have_console)
1770 SetConsoleMode(hIn, 0);
1772 WCMD_output_asis(anykey);
1773 WCMD_ReadFile(hIn, &key, 1, &count);
1774 if (have_console)
1775 SetConsoleMode(hIn, oldmode);
1778 /****************************************************************************
1779 * WCMD_remove_dir
1781 * Delete a directory.
1784 void WCMD_remove_dir (WCHAR *command) {
1786 int argno = 0;
1787 int argsProcessed = 0;
1788 WCHAR *argN = command;
1789 static const WCHAR parmS[] = {'/','S','\0'};
1790 static const WCHAR parmQ[] = {'/','Q','\0'};
1792 /* Loop through all args */
1793 while (argN) {
1794 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1795 if (argN && argN[0] != '/') {
1796 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1797 wine_dbgstr_w(quals));
1798 argsProcessed++;
1800 /* If subdirectory search not supplied, just try to remove
1801 and report error if it fails (eg if it contains a file) */
1802 if (strstrW (quals, parmS) == NULL) {
1803 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1805 /* Otherwise use ShFileOp to recursively remove a directory */
1806 } else {
1808 SHFILEOPSTRUCTW lpDir;
1810 /* Ask first */
1811 if (strstrW (quals, parmQ) == NULL) {
1812 BOOL ok;
1813 WCHAR question[MAXSTRING];
1814 static const WCHAR fmt[] = {'%','s',' ','\0'};
1816 /* Ask for confirmation */
1817 wsprintfW(question, fmt, thisArg);
1818 ok = WCMD_ask_confirm(question, TRUE, NULL);
1820 /* Abort if answer is 'N' */
1821 if (!ok) return;
1824 /* Do the delete */
1825 lpDir.hwnd = NULL;
1826 lpDir.pTo = NULL;
1827 lpDir.pFrom = thisArg;
1828 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1829 lpDir.wFunc = FO_DELETE;
1830 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1835 /* Handle no valid args */
1836 if (argsProcessed == 0) {
1837 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1838 return;
1843 /****************************************************************************
1844 * WCMD_rename
1846 * Rename a file.
1849 void WCMD_rename (void)
1851 int status;
1852 HANDLE hff;
1853 WIN32_FIND_DATAW fd;
1854 WCHAR input[MAX_PATH];
1855 WCHAR *dotDst = NULL;
1856 WCHAR drive[10];
1857 WCHAR dir[MAX_PATH];
1858 WCHAR fname[MAX_PATH];
1859 WCHAR ext[MAX_PATH];
1861 errorlevel = 0;
1863 /* Must be at least two args */
1864 if (param1[0] == 0x00 || param2[0] == 0x00) {
1865 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1866 errorlevel = 1;
1867 return;
1870 /* Destination cannot contain a drive letter or directory separator */
1871 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1872 SetLastError(ERROR_INVALID_PARAMETER);
1873 WCMD_print_error();
1874 errorlevel = 1;
1875 return;
1878 /* Convert partial path to full path */
1879 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1880 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1881 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1882 dotDst = strchrW(param2, '.');
1884 /* Split into components */
1885 WCMD_splitpath(input, drive, dir, fname, ext);
1887 hff = FindFirstFileW(input, &fd);
1888 if (hff == INVALID_HANDLE_VALUE)
1889 return;
1891 do {
1892 WCHAR dest[MAX_PATH];
1893 WCHAR src[MAX_PATH];
1894 WCHAR *dotSrc = NULL;
1895 int dirLen;
1897 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1899 /* FIXME: If dest name or extension is *, replace with filename/ext
1900 part otherwise use supplied name. This supports:
1901 ren *.fred *.jim
1902 ren jim.* fred.* etc
1903 However, windows has a more complex algorithm supporting eg
1904 ?'s and *'s mid name */
1905 dotSrc = strchrW(fd.cFileName, '.');
1907 /* Build src & dest name */
1908 strcpyW(src, drive);
1909 strcatW(src, dir);
1910 strcpyW(dest, src);
1911 dirLen = strlenW(src);
1912 strcatW(src, fd.cFileName);
1914 /* Build name */
1915 if (param2[0] == '*') {
1916 strcatW(dest, fd.cFileName);
1917 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1918 } else {
1919 strcatW(dest, param2);
1920 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1923 /* Build Extension */
1924 if (dotDst && (*(dotDst+1)=='*')) {
1925 if (dotSrc) strcatW(dest, dotSrc);
1926 } else if (dotDst) {
1927 if (dotDst) strcatW(dest, dotDst);
1930 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1931 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1933 status = MoveFileW(src, dest);
1935 if (!status) {
1936 WCMD_print_error ();
1937 errorlevel = 1;
1939 } while (FindNextFileW(hff, &fd) != 0);
1941 FindClose(hff);
1944 /*****************************************************************************
1945 * WCMD_dupenv
1947 * Make a copy of the environment.
1949 static WCHAR *WCMD_dupenv( const WCHAR *env )
1951 WCHAR *env_copy;
1952 int len;
1954 if( !env )
1955 return NULL;
1957 len = 0;
1958 while ( env[len] )
1959 len += (strlenW(&env[len]) + 1);
1961 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1962 if (!env_copy)
1964 WINE_ERR("out of memory\n");
1965 return env_copy;
1967 memcpy (env_copy, env, len*sizeof (WCHAR));
1968 env_copy[len] = 0;
1970 return env_copy;
1973 /*****************************************************************************
1974 * WCMD_setlocal
1976 * setlocal pushes the environment onto a stack
1977 * Save the environment as unicode so we don't screw anything up.
1979 void WCMD_setlocal (const WCHAR *s) {
1980 WCHAR *env;
1981 struct env_stack *env_copy;
1982 WCHAR cwd[MAX_PATH];
1984 /* DISABLEEXTENSIONS ignored */
1986 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1987 if( !env_copy )
1989 WINE_ERR ("out of memory\n");
1990 return;
1993 env = GetEnvironmentStringsW ();
1995 env_copy->strings = WCMD_dupenv (env);
1996 if (env_copy->strings)
1998 env_copy->next = saved_environment;
1999 saved_environment = env_copy;
2001 /* Save the current drive letter */
2002 GetCurrentDirectoryW(MAX_PATH, cwd);
2003 env_copy->u.cwd = cwd[0];
2005 else
2006 LocalFree (env_copy);
2008 FreeEnvironmentStringsW (env);
2012 /*****************************************************************************
2013 * WCMD_endlocal
2015 * endlocal pops the environment off a stack
2016 * Note: When searching for '=', search from WCHAR position 1, to handle
2017 * special internal environment variables =C:, =D: etc
2019 void WCMD_endlocal (void) {
2020 WCHAR *env, *old, *p;
2021 struct env_stack *temp;
2022 int len, n;
2024 if (!saved_environment)
2025 return;
2027 /* pop the old environment from the stack */
2028 temp = saved_environment;
2029 saved_environment = temp->next;
2031 /* delete the current environment, totally */
2032 env = GetEnvironmentStringsW ();
2033 old = WCMD_dupenv (GetEnvironmentStringsW ());
2034 len = 0;
2035 while (old[len]) {
2036 n = strlenW(&old[len]) + 1;
2037 p = strchrW(&old[len] + 1, '=');
2038 if (p)
2040 *p++ = 0;
2041 SetEnvironmentVariableW (&old[len], NULL);
2043 len += n;
2045 LocalFree (old);
2046 FreeEnvironmentStringsW (env);
2048 /* restore old environment */
2049 env = temp->strings;
2050 len = 0;
2051 while (env[len]) {
2052 n = strlenW(&env[len]) + 1;
2053 p = strchrW(&env[len] + 1, '=');
2054 if (p)
2056 *p++ = 0;
2057 SetEnvironmentVariableW (&env[len], p);
2059 len += n;
2062 /* Restore current drive letter */
2063 if (IsCharAlphaW(temp->u.cwd)) {
2064 WCHAR envvar[4];
2065 WCHAR cwd[MAX_PATH];
2066 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2068 wsprintfW(envvar, fmt, temp->u.cwd);
2069 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2070 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2071 SetCurrentDirectoryW(cwd);
2075 LocalFree (env);
2076 LocalFree (temp);
2079 /*****************************************************************************
2080 * WCMD_setshow_default
2082 * Set/Show the current default directory
2085 void WCMD_setshow_default (const WCHAR *command) {
2087 BOOL status;
2088 WCHAR string[1024];
2089 WCHAR cwd[1024];
2090 WCHAR *pos;
2091 WIN32_FIND_DATAW fd;
2092 HANDLE hff;
2093 static const WCHAR parmD[] = {'/','D','\0'};
2095 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2097 /* Skip /D and trailing whitespace if on the front of the command line */
2098 if (CompareStringW(LOCALE_USER_DEFAULT,
2099 NORM_IGNORECASE | SORT_STRINGSORT,
2100 command, 2, parmD, -1) == CSTR_EQUAL) {
2101 command += 2;
2102 while (*command && (*command==' ' || *command=='\t'))
2103 command++;
2106 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2107 if (strlenW(command) == 0) {
2108 strcatW (cwd, newlineW);
2109 WCMD_output_asis (cwd);
2111 else {
2112 /* Remove any double quotes, which may be in the
2113 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2114 pos = string;
2115 while (*command) {
2116 if (*command != '"') *pos++ = *command;
2117 command++;
2119 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2120 pos--;
2121 *pos = 0x00;
2123 /* Search for appropriate directory */
2124 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2125 hff = FindFirstFileW(string, &fd);
2126 if (hff != INVALID_HANDLE_VALUE) {
2127 do {
2128 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2129 WCHAR fpath[MAX_PATH];
2130 WCHAR drive[10];
2131 WCHAR dir[MAX_PATH];
2132 WCHAR fname[MAX_PATH];
2133 WCHAR ext[MAX_PATH];
2134 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2136 /* Convert path into actual directory spec */
2137 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2138 WCMD_splitpath(fpath, drive, dir, fname, ext);
2140 /* Rebuild path */
2141 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2142 break;
2144 } while (FindNextFileW(hff, &fd) != 0);
2145 FindClose(hff);
2148 /* Change to that directory */
2149 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2151 status = SetCurrentDirectoryW(string);
2152 if (!status) {
2153 errorlevel = 1;
2154 WCMD_print_error ();
2155 return;
2156 } else {
2158 /* Save away the actual new directory, to store as current location */
2159 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2161 /* Restore old directory if drive letter would change, and
2162 CD x:\directory /D (or pushd c:\directory) not supplied */
2163 if ((strstrW(quals, parmD) == NULL) &&
2164 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2165 SetCurrentDirectoryW(cwd);
2169 /* Set special =C: type environment variable, for drive letter of
2170 change of directory, even if path was restored due to missing
2171 /D (allows changing drive letter when not resident on that
2172 drive */
2173 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2174 WCHAR env[4];
2175 strcpyW(env, equalW);
2176 memcpy(env+1, string, 2 * sizeof(WCHAR));
2177 env[3] = 0x00;
2178 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2179 SetEnvironmentVariableW(env, string);
2183 return;
2186 /****************************************************************************
2187 * WCMD_setshow_date
2189 * Set/Show the system date
2190 * FIXME: Can't change date yet
2193 void WCMD_setshow_date (void) {
2195 WCHAR curdate[64], buffer[64];
2196 DWORD count;
2197 static const WCHAR parmT[] = {'/','T','\0'};
2199 if (strlenW(param1) == 0) {
2200 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2201 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2202 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2203 if (strstrW (quals, parmT) == NULL) {
2204 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2205 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2206 if (count > 2) {
2207 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2211 else WCMD_print_error ();
2213 else {
2214 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2218 /****************************************************************************
2219 * WCMD_compare
2221 static int WCMD_compare( const void *a, const void *b )
2223 int r;
2224 const WCHAR * const *str_a = a, * const *str_b = b;
2225 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2226 *str_a, -1, *str_b, -1 );
2227 if( r == CSTR_LESS_THAN ) return -1;
2228 if( r == CSTR_GREATER_THAN ) return 1;
2229 return 0;
2232 /****************************************************************************
2233 * WCMD_setshow_sortenv
2235 * sort variables into order for display
2236 * Optionally only display those who start with a stub
2237 * returns the count displayed
2239 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2241 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2242 const WCHAR **str;
2244 if (stub) stublen = strlenW(stub);
2246 /* count the number of strings, and the total length */
2247 while ( s[len] ) {
2248 len += (strlenW(&s[len]) + 1);
2249 count++;
2252 /* add the strings to an array */
2253 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2254 if( !str )
2255 return 0;
2256 str[0] = s;
2257 for( i=1; i<count; i++ )
2258 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2260 /* sort the array */
2261 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2263 /* print it */
2264 for( i=0; i<count; i++ ) {
2265 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2266 NORM_IGNORECASE | SORT_STRINGSORT,
2267 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2268 /* Don't display special internal variables */
2269 if (str[i][0] != '=') {
2270 WCMD_output_asis(str[i]);
2271 WCMD_output_asis(newlineW);
2272 displayedcount++;
2277 LocalFree( str );
2278 return displayedcount;
2281 /****************************************************************************
2282 * WCMD_setshow_env
2284 * Set/Show the environment variables
2287 void WCMD_setshow_env (WCHAR *s) {
2289 LPVOID env;
2290 WCHAR *p;
2291 int status;
2292 static const WCHAR parmP[] = {'/','P','\0'};
2294 if (param1[0] == 0x00 && quals[0] == 0x00) {
2295 env = GetEnvironmentStringsW();
2296 WCMD_setshow_sortenv( env, NULL );
2297 return;
2300 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2301 if (CompareStringW(LOCALE_USER_DEFAULT,
2302 NORM_IGNORECASE | SORT_STRINGSORT,
2303 s, 2, parmP, -1) == CSTR_EQUAL) {
2304 WCHAR string[MAXSTRING];
2305 DWORD count;
2307 s += 2;
2308 while (*s && (*s==' ' || *s=='\t')) s++;
2309 if (*s=='\"')
2310 WCMD_strip_quotes(s);
2312 /* If no parameter, or no '=' sign, return an error */
2313 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2314 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2315 return;
2318 /* Output the prompt */
2319 *p++ = '\0';
2320 if (strlenW(p) != 0) WCMD_output_asis(p);
2322 /* Read the reply */
2323 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2324 if (count > 1) {
2325 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2326 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2327 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2328 wine_dbgstr_w(string));
2329 status = SetEnvironmentVariableW(s, string);
2332 } else {
2333 DWORD gle;
2335 if (*s=='\"')
2336 WCMD_strip_quotes(s);
2337 p = strchrW (s, '=');
2338 if (p == NULL) {
2339 env = GetEnvironmentStringsW();
2340 if (WCMD_setshow_sortenv( env, s ) == 0) {
2341 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2342 errorlevel = 1;
2344 return;
2346 *p++ = '\0';
2348 if (strlenW(p) == 0) p = NULL;
2349 status = SetEnvironmentVariableW(s, p);
2350 gle = GetLastError();
2351 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2352 errorlevel = 1;
2353 } else if ((!status)) WCMD_print_error();
2357 /****************************************************************************
2358 * WCMD_setshow_path
2360 * Set/Show the path environment variable
2363 void WCMD_setshow_path (const WCHAR *command) {
2365 WCHAR string[1024];
2366 DWORD status;
2367 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2368 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2370 if (strlenW(param1) == 0) {
2371 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2372 if (status != 0) {
2373 WCMD_output_asis ( pathEqW);
2374 WCMD_output_asis ( string);
2375 WCMD_output_asis ( newlineW);
2377 else {
2378 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2381 else {
2382 if (*command == '=') command++; /* Skip leading '=' */
2383 status = SetEnvironmentVariableW(pathW, command);
2384 if (!status) WCMD_print_error();
2388 /****************************************************************************
2389 * WCMD_setshow_prompt
2391 * Set or show the command prompt.
2394 void WCMD_setshow_prompt (void) {
2396 WCHAR *s;
2397 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2399 if (strlenW(param1) == 0) {
2400 SetEnvironmentVariableW(promptW, NULL);
2402 else {
2403 s = param1;
2404 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2405 if (strlenW(s) == 0) {
2406 SetEnvironmentVariableW(promptW, NULL);
2408 else SetEnvironmentVariableW(promptW, s);
2412 /****************************************************************************
2413 * WCMD_setshow_time
2415 * Set/Show the system time
2416 * FIXME: Can't change time yet
2419 void WCMD_setshow_time (void) {
2421 WCHAR curtime[64], buffer[64];
2422 DWORD count;
2423 SYSTEMTIME st;
2424 static const WCHAR parmT[] = {'/','T','\0'};
2426 if (strlenW(param1) == 0) {
2427 GetLocalTime(&st);
2428 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2429 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2430 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2431 if (strstrW (quals, parmT) == NULL) {
2432 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2433 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2434 if (count > 2) {
2435 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2439 else WCMD_print_error ();
2441 else {
2442 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2446 /****************************************************************************
2447 * WCMD_shift
2449 * Shift batch parameters.
2450 * Optional /n says where to start shifting (n=0-8)
2453 void WCMD_shift (const WCHAR *command) {
2454 int start;
2456 if (context != NULL) {
2457 WCHAR *pos = strchrW(command, '/');
2458 int i;
2460 if (pos == NULL) {
2461 start = 0;
2462 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2463 start = (*(pos+1) - '0');
2464 } else {
2465 SetLastError(ERROR_INVALID_PARAMETER);
2466 WCMD_print_error();
2467 return;
2470 WINE_TRACE("Shifting variables, starting at %d\n", start);
2471 for (i=start;i<=8;i++) {
2472 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2474 context -> shift_count[9] = context -> shift_count[9] + 1;
2479 /****************************************************************************
2480 * WCMD_start
2482 void WCMD_start(const WCHAR *command)
2484 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
2485 '\\','s','t','a','r','t','.','e','x','e',0};
2486 WCHAR file[MAX_PATH];
2487 WCHAR *cmdline;
2488 STARTUPINFOW st;
2489 PROCESS_INFORMATION pi;
2491 GetWindowsDirectoryW( file, MAX_PATH );
2492 strcatW( file, exeW );
2493 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
2494 strcpyW( cmdline, file );
2495 strcatW( cmdline, spaceW );
2496 strcatW( cmdline, command );
2498 memset( &st, 0, sizeof(STARTUPINFOW) );
2499 st.cb = sizeof(STARTUPINFOW);
2501 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
2503 WaitForSingleObject( pi.hProcess, INFINITE );
2504 GetExitCodeProcess( pi.hProcess, &errorlevel );
2505 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
2506 CloseHandle(pi.hProcess);
2507 CloseHandle(pi.hThread);
2509 else
2511 SetLastError(ERROR_FILE_NOT_FOUND);
2512 WCMD_print_error ();
2513 errorlevel = 9009;
2515 HeapFree( GetProcessHeap(), 0, cmdline );
2518 /****************************************************************************
2519 * WCMD_title
2521 * Set the console title
2523 void WCMD_title (const WCHAR *command) {
2524 SetConsoleTitleW(command);
2527 /****************************************************************************
2528 * WCMD_type
2530 * Copy a file to standard output.
2533 void WCMD_type (WCHAR *command) {
2535 int argno = 0;
2536 WCHAR *argN = command;
2537 BOOL writeHeaders = FALSE;
2539 if (param1[0] == 0x00) {
2540 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2541 return;
2544 if (param2[0] != 0x00) writeHeaders = TRUE;
2546 /* Loop through all args */
2547 errorlevel = 0;
2548 while (argN) {
2549 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2551 HANDLE h;
2552 WCHAR buffer[512];
2553 DWORD count;
2555 if (!argN) break;
2557 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2558 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2559 FILE_ATTRIBUTE_NORMAL, NULL);
2560 if (h == INVALID_HANDLE_VALUE) {
2561 WCMD_print_error ();
2562 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2563 errorlevel = 1;
2564 } else {
2565 if (writeHeaders) {
2566 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2567 WCMD_output(fmt, thisArg);
2569 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2570 if (count == 0) break; /* ReadFile reports success on EOF! */
2571 buffer[count] = 0;
2572 WCMD_output_asis (buffer);
2574 CloseHandle (h);
2579 /****************************************************************************
2580 * WCMD_more
2582 * Output either a file or stdin to screen in pages
2585 void WCMD_more (WCHAR *command) {
2587 int argno = 0;
2588 WCHAR *argN = command;
2589 WCHAR moreStr[100];
2590 WCHAR moreStrPage[100];
2591 WCHAR buffer[512];
2592 DWORD count;
2593 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2594 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2595 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2596 ')',' ','-','-','\n','\0'};
2597 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2599 /* Prefix the NLS more with '-- ', then load the text */
2600 errorlevel = 0;
2601 strcpyW(moreStr, moreStart);
2602 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2603 (sizeof(moreStr)/sizeof(WCHAR))-3);
2605 if (param1[0] == 0x00) {
2607 /* Wine implements pipes via temporary files, and hence stdin is
2608 effectively reading from the file. This means the prompts for
2609 more are satisfied by the next line from the input (file). To
2610 avoid this, ensure stdin is to the console */
2611 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2612 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2613 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2614 FILE_ATTRIBUTE_NORMAL, 0);
2615 WINE_TRACE("No parms - working probably in pipe mode\n");
2616 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2618 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2619 once you get in this bit unless due to a pipe, its going to end badly... */
2620 wsprintfW(moreStrPage, moreFmt, moreStr);
2622 WCMD_enter_paged_mode(moreStrPage);
2623 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2624 if (count == 0) break; /* ReadFile reports success on EOF! */
2625 buffer[count] = 0;
2626 WCMD_output_asis (buffer);
2628 WCMD_leave_paged_mode();
2630 /* Restore stdin to what it was */
2631 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2632 CloseHandle(hConIn);
2634 return;
2635 } else {
2636 BOOL needsPause = FALSE;
2638 /* Loop through all args */
2639 WINE_TRACE("Parms supplied - working through each file\n");
2640 WCMD_enter_paged_mode(moreStrPage);
2642 while (argN) {
2643 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2644 HANDLE h;
2646 if (!argN) break;
2648 if (needsPause) {
2650 /* Wait */
2651 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2652 WCMD_leave_paged_mode();
2653 WCMD_output_asis(moreStrPage);
2654 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2655 WCMD_enter_paged_mode(moreStrPage);
2659 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2660 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2661 FILE_ATTRIBUTE_NORMAL, NULL);
2662 if (h == INVALID_HANDLE_VALUE) {
2663 WCMD_print_error ();
2664 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2665 errorlevel = 1;
2666 } else {
2667 ULONG64 curPos = 0;
2668 ULONG64 fileLen = 0;
2669 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2671 /* Get the file size */
2672 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2673 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2675 needsPause = TRUE;
2676 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2677 if (count == 0) break; /* ReadFile reports success on EOF! */
2678 buffer[count] = 0;
2679 curPos += count;
2681 /* Update % count (would be used in WCMD_output_asis as prompt) */
2682 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2684 WCMD_output_asis (buffer);
2686 CloseHandle (h);
2690 WCMD_leave_paged_mode();
2694 /****************************************************************************
2695 * WCMD_verify
2697 * Display verify flag.
2698 * FIXME: We don't actually do anything with the verify flag other than toggle
2699 * it...
2702 void WCMD_verify (const WCHAR *command) {
2704 int count;
2706 count = strlenW(command);
2707 if (count == 0) {
2708 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2709 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2710 return;
2712 if (lstrcmpiW(command, onW) == 0) {
2713 verify_mode = TRUE;
2714 return;
2716 else if (lstrcmpiW(command, offW) == 0) {
2717 verify_mode = FALSE;
2718 return;
2720 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2723 /****************************************************************************
2724 * WCMD_version
2726 * Display version info.
2729 void WCMD_version (void) {
2731 WCMD_output_asis (version_string);
2735 /****************************************************************************
2736 * WCMD_volume
2738 * Display volume information (set_label = FALSE)
2739 * Additionally set volume label (set_label = TRUE)
2740 * Returns 1 on success, 0 otherwise
2743 int WCMD_volume(BOOL set_label, const WCHAR *path)
2745 DWORD count, serial;
2746 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2747 BOOL status;
2749 if (strlenW(path) == 0) {
2750 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2751 if (!status) {
2752 WCMD_print_error ();
2753 return 0;
2755 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2756 &serial, NULL, NULL, NULL, 0);
2758 else {
2759 static const WCHAR fmt[] = {'%','s','\\','\0'};
2760 if ((path[1] != ':') || (strlenW(path) != 2)) {
2761 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2762 return 0;
2764 wsprintfW (curdir, fmt, path);
2765 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2766 &serial, NULL,
2767 NULL, NULL, 0);
2769 if (!status) {
2770 WCMD_print_error ();
2771 return 0;
2773 if (label[0] != '\0') {
2774 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
2775 curdir[0], label);
2777 else {
2778 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
2779 curdir[0]);
2781 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
2782 HIWORD(serial), LOWORD(serial));
2783 if (set_label) {
2784 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2785 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2786 if (count > 1) {
2787 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2788 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2790 if (strlenW(path) != 0) {
2791 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2793 else {
2794 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2797 return 1;
2800 /**************************************************************************
2801 * WCMD_exit
2803 * Exit either the process, or just this batch program
2807 void WCMD_exit (CMD_LIST **cmdList) {
2809 static const WCHAR parmB[] = {'/','B','\0'};
2810 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2812 if (context && lstrcmpiW(quals, parmB) == 0) {
2813 errorlevel = rc;
2814 context -> skip_rest = TRUE;
2815 *cmdList = NULL;
2816 } else {
2817 ExitProcess(rc);
2822 /*****************************************************************************
2823 * WCMD_assoc
2825 * Lists or sets file associations (assoc = TRUE)
2826 * Lists or sets file types (assoc = FALSE)
2828 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2830 HKEY key;
2831 DWORD accessOptions = KEY_READ;
2832 WCHAR *newValue;
2833 LONG rc = ERROR_SUCCESS;
2834 WCHAR keyValue[MAXSTRING];
2835 DWORD valueLen = MAXSTRING;
2836 HKEY readKey;
2837 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2838 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2840 /* See if parameter includes '=' */
2841 errorlevel = 0;
2842 newValue = strchrW(command, '=');
2843 if (newValue) accessOptions |= KEY_WRITE;
2845 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2846 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2847 accessOptions, &key) != ERROR_SUCCESS) {
2848 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2849 return;
2852 /* If no parameters then list all associations */
2853 if (*command == 0x00) {
2854 int index = 0;
2856 /* Enumerate all the keys */
2857 while (rc != ERROR_NO_MORE_ITEMS) {
2858 WCHAR keyName[MAXSTRING];
2859 DWORD nameLen;
2861 /* Find the next value */
2862 nameLen = MAXSTRING;
2863 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2865 if (rc == ERROR_SUCCESS) {
2867 /* Only interested in extension ones if assoc, or others
2868 if not assoc */
2869 if ((keyName[0] == '.' && assoc) ||
2870 (!(keyName[0] == '.') && (!assoc)))
2872 WCHAR subkey[MAXSTRING];
2873 strcpyW(subkey, keyName);
2874 if (!assoc) strcatW(subkey, shOpCmdW);
2876 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2878 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2879 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2880 WCMD_output_asis(keyName);
2881 WCMD_output_asis(equalW);
2882 /* If no default value found, leave line empty after '=' */
2883 if (rc == ERROR_SUCCESS) {
2884 WCMD_output_asis(keyValue);
2886 WCMD_output_asis(newlineW);
2887 RegCloseKey(readKey);
2893 } else {
2895 /* Parameter supplied - if no '=' on command line, its a query */
2896 if (newValue == NULL) {
2897 WCHAR *space;
2898 WCHAR subkey[MAXSTRING];
2900 /* Query terminates the parameter at the first space */
2901 strcpyW(keyValue, command);
2902 space = strchrW(keyValue, ' ');
2903 if (space) *space=0x00;
2905 /* Set up key name */
2906 strcpyW(subkey, keyValue);
2907 if (!assoc) strcatW(subkey, shOpCmdW);
2909 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2911 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2912 WCMD_output_asis(command);
2913 WCMD_output_asis(equalW);
2914 /* If no default value found, leave line empty after '=' */
2915 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2916 WCMD_output_asis(newlineW);
2917 RegCloseKey(readKey);
2919 } else {
2920 WCHAR msgbuffer[MAXSTRING];
2922 /* Load the translated 'File association not found' */
2923 if (assoc) {
2924 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2925 } else {
2926 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2928 WCMD_output_stderr(msgbuffer, keyValue);
2929 errorlevel = 2;
2932 /* Not a query - its a set or clear of a value */
2933 } else {
2935 WCHAR subkey[MAXSTRING];
2937 /* Get pointer to new value */
2938 *newValue = 0x00;
2939 newValue++;
2941 /* Set up key name */
2942 strcpyW(subkey, command);
2943 if (!assoc) strcatW(subkey, shOpCmdW);
2945 /* If nothing after '=' then clear value - only valid for ASSOC */
2946 if (*newValue == 0x00) {
2948 if (assoc) rc = RegDeleteKeyW(key, command);
2949 if (assoc && rc == ERROR_SUCCESS) {
2950 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2952 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2953 WCMD_print_error();
2954 errorlevel = 2;
2956 } else {
2957 WCHAR msgbuffer[MAXSTRING];
2959 /* Load the translated 'File association not found' */
2960 if (assoc) {
2961 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2962 sizeof(msgbuffer)/sizeof(WCHAR));
2963 } else {
2964 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2965 sizeof(msgbuffer)/sizeof(WCHAR));
2967 WCMD_output_stderr(msgbuffer, keyValue);
2968 errorlevel = 2;
2971 /* It really is a set value = contents */
2972 } else {
2973 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2974 accessOptions, NULL, &readKey, NULL);
2975 if (rc == ERROR_SUCCESS) {
2976 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2977 (LPBYTE)newValue,
2978 sizeof(WCHAR) * (strlenW(newValue) + 1));
2979 RegCloseKey(readKey);
2982 if (rc != ERROR_SUCCESS) {
2983 WCMD_print_error();
2984 errorlevel = 2;
2985 } else {
2986 WCMD_output_asis(command);
2987 WCMD_output_asis(equalW);
2988 WCMD_output_asis(newValue);
2989 WCMD_output_asis(newlineW);
2995 /* Clean up */
2996 RegCloseKey(key);
2999 /****************************************************************************
3000 * WCMD_color
3002 * Colors the terminal screen.
3005 void WCMD_color (void) {
3007 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3008 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3010 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3011 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3012 return;
3015 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3017 COORD topLeft;
3018 DWORD screenSize;
3019 DWORD color = 0;
3021 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3023 topLeft.X = 0;
3024 topLeft.Y = 0;
3026 /* Convert the color hex digits */
3027 if (param1[0] == 0x00) {
3028 color = defaultColor;
3029 } else {
3030 color = strtoulW(param1, NULL, 16);
3033 /* Fail if fg == bg color */
3034 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3035 errorlevel = 1;
3036 return;
3039 /* Set the current screen contents and ensure all future writes
3040 remain this color */
3041 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3042 SetConsoleTextAttribute(hStdOut, color);