fusion: Account for the version prefix when building the file mask.
[wine/multimedia.git] / programs / cmd / builtins.c
blobebb1c80110325ae724e14044e160dec8ceaafd55
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 conditionTRUE)
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 conditionTRUE);
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 (conditionTRUE && 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 = TRUE;
999 if (isIF) processThese = conditionTRUE;
1001 while (*cmdList) {
1002 static const WCHAR ifElse[] = {'e','l','s','e'};
1004 /* execute all appropriate commands */
1005 curPosition = *cmdList;
1007 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1008 *cmdList,
1009 (*cmdList)->prevDelim,
1010 (*cmdList)->bracketDepth, myDepth);
1012 /* Execute any statements appended to the line */
1013 /* FIXME: Only if previous call worked for && or failed for || */
1014 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1015 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1016 if (processThese && (*cmdList)->command) {
1017 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1018 value, cmdList);
1020 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1022 /* Execute any appended to the statement with (...) */
1023 } else if ((*cmdList)->bracketDepth > myDepth) {
1024 if (processThese) {
1025 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1026 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1028 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1030 /* End of the command - does 'ELSE ' follow as the next command? */
1031 } else {
1032 if (isIF
1033 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1034 (*cmdList)->command)) {
1036 /* Swap between if and else processing */
1037 processThese = !processThese;
1039 /* Process the ELSE part */
1040 if (processThese) {
1041 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1042 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1044 /* Skip leading whitespace between condition and the command */
1045 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1046 if (*cmd) {
1047 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1050 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1051 } else {
1052 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1053 break;
1058 return;
1061 /**************************************************************************
1062 * WCMD_for
1064 * Batch file loop processing.
1066 * On entry: cmdList contains the syntax up to the set
1067 * next cmdList and all in that bracket contain the set data
1068 * next cmdlist contains the DO cmd
1069 * following that is either brackets or && entries (as per if)
1073 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1075 WIN32_FIND_DATAW fd;
1076 HANDLE hff;
1077 int i;
1078 static const WCHAR inW[] = {'i','n'};
1079 static const WCHAR doW[] = {'d','o'};
1080 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1081 WCHAR variable[4];
1082 WCHAR *firstCmd;
1083 int thisDepth;
1085 WCHAR *curPos = p;
1086 BOOL expandDirs = FALSE;
1087 BOOL useNumbers = FALSE;
1088 BOOL doFileset = FALSE;
1089 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1090 int itemNum;
1091 CMD_LIST *thisCmdStart;
1094 /* Handle optional qualifiers (multiple are allowed) */
1095 while (*curPos && *curPos == '/') {
1096 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
1097 curPos++;
1098 switch (toupperW(*curPos)) {
1099 case 'D': curPos++; expandDirs = TRUE; break;
1100 case 'L': curPos++; useNumbers = TRUE; break;
1102 /* Recursive is special case - /R can have an optional path following it */
1103 /* filenamesets are another special case - /F can have an optional options following it */
1104 case 'R':
1105 case 'F':
1107 BOOL isRecursive = (*curPos == 'R');
1109 if (!isRecursive)
1110 doFileset = TRUE;
1112 /* Skip whitespace */
1113 curPos++;
1114 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1116 /* Next parm is either qualifier, path/options or variable -
1117 only care about it if it is the path/options */
1118 if (*curPos && *curPos != '/' && *curPos != '%') {
1119 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
1120 else {
1121 static unsigned int once;
1122 if (!once++) WINE_FIXME("/F needs to handle options\n");
1125 break;
1127 default:
1128 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
1129 curPos++;
1132 /* Skip whitespace between qualifiers */
1133 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1136 /* Skip whitespace before variable */
1137 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1139 /* Ensure line continues with variable */
1140 if (!*curPos || *curPos != '%') {
1141 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1142 return;
1145 /* Variable should follow */
1146 i = 0;
1147 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
1148 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
1149 variable[i] = 0x00;
1150 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1151 curPos = &curPos[i];
1153 /* Skip whitespace before IN */
1154 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1156 /* Ensure line continues with IN */
1157 if (!*curPos
1158 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
1160 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1161 return;
1164 /* Save away where the set of data starts and the variable */
1165 thisDepth = (*cmdList)->bracketDepth;
1166 *cmdList = (*cmdList)->nextcommand;
1167 setStart = (*cmdList);
1169 /* Skip until the close bracket */
1170 WINE_TRACE("Searching %p as the set\n", *cmdList);
1171 while (*cmdList &&
1172 (*cmdList)->command != NULL &&
1173 (*cmdList)->bracketDepth > thisDepth) {
1174 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1175 *cmdList = (*cmdList)->nextcommand;
1178 /* Skip the close bracket, if there is one */
1179 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1181 /* Syntax error if missing close bracket, or nothing following it
1182 and once we have the complete set, we expect a DO */
1183 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1184 if ((*cmdList == NULL)
1185 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1187 WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
1188 return;
1191 /* Save away the starting position for the commands (and offset for the
1192 first one */
1193 cmdStart = *cmdList;
1194 cmdEnd = *cmdList;
1195 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1196 itemNum = 0;
1198 thisSet = setStart;
1199 /* Loop through all set entries */
1200 while (thisSet &&
1201 thisSet->command != NULL &&
1202 thisSet->bracketDepth >= thisDepth) {
1204 /* Loop through all entries on the same line */
1205 WCHAR *item;
1206 WCHAR *itemStart;
1208 WINE_TRACE("Processing for set %p\n", thisSet);
1209 i = 0;
1210 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1213 * If the parameter within the set has a wildcard then search for matching files
1214 * otherwise do a literal substitution.
1216 static const WCHAR wildcards[] = {'*','?','\0'};
1217 thisCmdStart = cmdStart;
1219 itemNum++;
1220 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1222 if (!useNumbers && !doFileset) {
1223 if (strpbrkW (item, wildcards)) {
1224 hff = FindFirstFileW(item, &fd);
1225 if (hff != INVALID_HANDLE_VALUE) {
1226 do {
1227 BOOL isDirectory = FALSE;
1229 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1231 /* Handle as files or dirs appropriately, but ignore . and .. */
1232 if (isDirectory == expandDirs &&
1233 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1234 (strcmpW(fd.cFileName, dotW) != 0))
1236 thisCmdStart = cmdStart;
1237 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1238 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1239 fd.cFileName, FALSE, TRUE);
1242 } while (FindNextFileW(hff, &fd) != 0);
1243 FindClose (hff);
1245 } else {
1246 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1249 } else if (useNumbers) {
1250 /* Convert the first 3 numbers to signed longs and save */
1251 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1252 /* else ignore them! */
1254 /* Filesets - either a list of files, or a command to run and parse the output */
1255 } else if (doFileset && *itemStart != '"') {
1257 HANDLE input;
1258 WCHAR temp_file[MAX_PATH];
1260 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1261 wine_dbgstr_w(item));
1263 /* If backquote or single quote, we need to launch that command
1264 and parse the results - use a temporary file */
1265 if (*itemStart == '`' || *itemStart == '\'') {
1267 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1268 static const WCHAR redirOut[] = {'>','%','s','\0'};
1269 static const WCHAR cmdW[] = {'C','M','D','\0'};
1271 /* Remove trailing character */
1272 itemStart[strlenW(itemStart)-1] = 0x00;
1274 /* Get temp filename */
1275 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1276 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1278 /* Execute program and redirect output */
1279 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1280 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1282 /* Open the file, read line by line and process */
1283 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1284 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1285 } else {
1287 /* Open the file, read line by line and process */
1288 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1289 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1292 /* Process the input file */
1293 if (input == INVALID_HANDLE_VALUE) {
1294 WCMD_print_error ();
1295 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1296 errorlevel = 1;
1297 return; /* FOR loop aborts at first failure here */
1299 } else {
1301 WCHAR buffer[MAXSTRING] = {'\0'};
1302 WCHAR *where, *parm;
1304 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1306 /* Skip blank lines*/
1307 parm = WCMD_parameter (buffer, 0, &where, NULL);
1308 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1309 wine_dbgstr_w(buffer));
1311 if (where) {
1312 /* FIXME: The following should be moved into its own routine and
1313 reused for the string literal parsing below */
1314 thisCmdStart = cmdStart;
1315 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1316 cmdEnd = thisCmdStart;
1319 buffer[0] = 0x00;
1322 CloseHandle (input);
1325 /* Delete the temporary file */
1326 if (*itemStart == '`' || *itemStart == '\'') {
1327 DeleteFileW(temp_file);
1330 /* Filesets - A string literal */
1331 } else if (doFileset && *itemStart == '"') {
1332 WCHAR buffer[MAXSTRING] = {'\0'};
1333 WCHAR *where, *parm;
1335 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1336 strcpyW(buffer, item);
1337 parm = WCMD_parameter (buffer, 0, &where, NULL);
1338 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1339 wine_dbgstr_w(buffer));
1341 if (where) {
1342 /* FIXME: The following should be moved into its own routine and
1343 reused for the string literal parsing below */
1344 thisCmdStart = cmdStart;
1345 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1346 cmdEnd = thisCmdStart;
1350 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1351 cmdEnd = thisCmdStart;
1352 i++;
1355 /* Move onto the next set line */
1356 thisSet = thisSet->nextcommand;
1359 /* If /L is provided, now run the for loop */
1360 if (useNumbers) {
1361 WCHAR thisNum[20];
1362 static const WCHAR fmt[] = {'%','d','\0'};
1364 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1365 numbers[0], numbers[2], numbers[1]);
1366 for (i=numbers[0];
1367 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1368 i=i + numbers[1]) {
1370 sprintfW(thisNum, fmt, i);
1371 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1373 thisCmdStart = cmdStart;
1374 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1375 cmdEnd = thisCmdStart;
1379 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1380 all processing, OR it should be pointing to the end of && processing OR
1381 it should be pointing at the NULL end of bracket for the DO. The return
1382 value needs to be the NEXT command to execute, which it either is, or
1383 we need to step over the closing bracket */
1384 *cmdList = cmdEnd;
1385 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1388 /**************************************************************************
1389 * WCMD_give_help
1391 * Simple on-line help. Help text is stored in the resource file.
1394 void WCMD_give_help (const WCHAR *command)
1396 size_t i;
1398 command = WCMD_skip_leading_spaces((WCHAR*) command);
1399 if (strlenW(command) == 0) {
1400 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1402 else {
1403 /* Display help message for builtin commands */
1404 for (i=0; i<=WCMD_EXIT; i++) {
1405 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1406 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1407 WCMD_output_asis (WCMD_LoadMessage(i));
1408 return;
1411 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1412 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1413 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1414 command, -1, externals[i], -1) == CSTR_EQUAL) {
1415 WCHAR cmd[128];
1416 static const WCHAR helpW[] = {' ', '/','?','\0'};
1417 strcpyW(cmd, command);
1418 strcatW(cmd, helpW);
1419 WCMD_run_program(cmd, FALSE);
1420 return;
1423 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1425 return;
1428 /****************************************************************************
1429 * WCMD_go_to
1431 * Batch file jump instruction. Not the most efficient algorithm ;-)
1432 * Prints error message if the specified label cannot be found - the file pointer is
1433 * then at EOF, effectively stopping the batch file.
1434 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1437 void WCMD_goto (CMD_LIST **cmdList) {
1439 WCHAR string[MAX_PATH];
1440 WCHAR current[MAX_PATH];
1442 /* Do not process any more parts of a processed multipart or multilines command */
1443 if (cmdList) *cmdList = NULL;
1445 if (context != NULL) {
1446 WCHAR *paramStart = param1, *str;
1447 static const WCHAR eofW[] = {':','e','o','f','\0'};
1449 if (param1[0] == 0x00) {
1450 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1451 return;
1454 /* Handle special :EOF label */
1455 if (lstrcmpiW (eofW, param1) == 0) {
1456 context -> skip_rest = TRUE;
1457 return;
1460 /* Support goto :label as well as goto label */
1461 if (*paramStart == ':') paramStart++;
1463 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1464 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1465 str = string;
1466 while (isspaceW (*str)) str++;
1467 if (*str == ':') {
1468 DWORD index = 0;
1469 str++;
1470 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1471 index++;
1473 /* ignore space at the end */
1474 current[index] = 0;
1475 if (lstrcmpiW (current, paramStart) == 0) return;
1478 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1480 return;
1483 /*****************************************************************************
1484 * WCMD_pushd
1486 * Push a directory onto the stack
1489 void WCMD_pushd (const WCHAR *command)
1491 struct env_stack *curdir;
1492 WCHAR *thisdir;
1493 static const WCHAR parmD[] = {'/','D','\0'};
1495 if (strchrW(command, '/') != NULL) {
1496 SetLastError(ERROR_INVALID_PARAMETER);
1497 WCMD_print_error();
1498 return;
1501 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1502 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1503 if( !curdir || !thisdir ) {
1504 LocalFree(curdir);
1505 LocalFree(thisdir);
1506 WINE_ERR ("out of memory\n");
1507 return;
1510 /* Change directory using CD code with /D parameter */
1511 strcpyW(quals, parmD);
1512 GetCurrentDirectoryW (1024, thisdir);
1513 errorlevel = 0;
1514 WCMD_setshow_default(command);
1515 if (errorlevel) {
1516 LocalFree(curdir);
1517 LocalFree(thisdir);
1518 return;
1519 } else {
1520 curdir -> next = pushd_directories;
1521 curdir -> strings = thisdir;
1522 if (pushd_directories == NULL) {
1523 curdir -> u.stackdepth = 1;
1524 } else {
1525 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1527 pushd_directories = curdir;
1532 /*****************************************************************************
1533 * WCMD_popd
1535 * Pop a directory from the stack
1538 void WCMD_popd (void) {
1539 struct env_stack *temp = pushd_directories;
1541 if (!pushd_directories)
1542 return;
1544 /* pop the old environment from the stack, and make it the current dir */
1545 pushd_directories = temp->next;
1546 SetCurrentDirectoryW(temp->strings);
1547 LocalFree (temp->strings);
1548 LocalFree (temp);
1551 /****************************************************************************
1552 * WCMD_if
1554 * Batch file conditional.
1556 * On entry, cmdlist will point to command containing the IF, and optionally
1557 * the first command to execute (if brackets not found)
1558 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1559 * If ('s were found, execute all within that bracket
1560 * Command may optionally be followed by an ELSE - need to skip instructions
1561 * in the else using the same logic
1563 * FIXME: Much more syntax checking needed!
1566 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1568 int negate; /* Negate condition */
1569 int test; /* Condition evaluation result */
1570 WCHAR condition[MAX_PATH], *command, *s;
1571 static const WCHAR notW[] = {'n','o','t','\0'};
1572 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1573 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1574 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1575 static const WCHAR eqeqW[] = {'=','=','\0'};
1576 static const WCHAR parmI[] = {'/','I','\0'};
1577 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1579 negate = !lstrcmpiW(param1,notW);
1580 strcpyW(condition, (negate ? param2 : param1));
1581 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1583 if (!lstrcmpiW (condition, errlvlW)) {
1584 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL);
1585 WCHAR *endptr;
1586 long int param_int = strtolW(param, &endptr, 10);
1587 if (*endptr) {
1588 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1589 return;
1591 test = ((long int)errorlevel >= param_int);
1592 WCMD_parameter(p, 2+negate, &command, NULL);
1594 else if (!lstrcmpiW (condition, existW)) {
1595 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1596 WCMD_parameter(p, 2+negate, &command, NULL);
1598 else if (!lstrcmpiW (condition, defdW)) {
1599 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1600 WCMD_parameter(p, 2+negate, &command, NULL);
1602 else if ((s = strstrW (p, eqeqW))) {
1603 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1604 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1605 s += 2;
1606 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1607 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1608 test = caseInsensitive
1609 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1610 leftPart, leftPartEnd-leftPart+1,
1611 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1612 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1613 leftPart, leftPartEnd-leftPart+1,
1614 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1615 WCMD_parameter(s, 1, &command, NULL);
1617 else {
1618 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1619 return;
1622 /* Process rest of IF statement which is on the same line
1623 Note: This may process all or some of the cmdList (eg a GOTO) */
1624 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1627 /****************************************************************************
1628 * WCMD_move
1630 * Move a file, directory tree or wildcarded set of files.
1633 void WCMD_move (void)
1635 int status;
1636 WIN32_FIND_DATAW fd;
1637 HANDLE hff;
1638 WCHAR input[MAX_PATH];
1639 WCHAR output[MAX_PATH];
1640 WCHAR drive[10];
1641 WCHAR dir[MAX_PATH];
1642 WCHAR fname[MAX_PATH];
1643 WCHAR ext[MAX_PATH];
1645 if (param1[0] == 0x00) {
1646 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1647 return;
1650 /* If no destination supplied, assume current directory */
1651 if (param2[0] == 0x00) {
1652 strcpyW(param2, dotW);
1655 /* If 2nd parm is directory, then use original filename */
1656 /* Convert partial path to full path */
1657 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1658 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1659 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1660 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1662 /* Split into components */
1663 WCMD_splitpath(input, drive, dir, fname, ext);
1665 hff = FindFirstFileW(input, &fd);
1666 if (hff == INVALID_HANDLE_VALUE)
1667 return;
1669 do {
1670 WCHAR dest[MAX_PATH];
1671 WCHAR src[MAX_PATH];
1672 DWORD attribs;
1673 BOOL ok = TRUE;
1675 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1677 /* Build src & dest name */
1678 strcpyW(src, drive);
1679 strcatW(src, dir);
1681 /* See if dest is an existing directory */
1682 attribs = GetFileAttributesW(output);
1683 if (attribs != INVALID_FILE_ATTRIBUTES &&
1684 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1685 strcpyW(dest, output);
1686 strcatW(dest, slashW);
1687 strcatW(dest, fd.cFileName);
1688 } else {
1689 strcpyW(dest, output);
1692 strcatW(src, fd.cFileName);
1694 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1695 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1697 /* If destination exists, prompt unless /Y supplied */
1698 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1699 BOOL force = FALSE;
1700 WCHAR copycmd[MAXSTRING];
1701 DWORD len;
1703 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1704 if (strstrW (quals, parmNoY))
1705 force = FALSE;
1706 else if (strstrW (quals, parmY))
1707 force = TRUE;
1708 else {
1709 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1710 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1711 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1712 && ! lstrcmpiW (copycmd, parmY));
1715 /* Prompt if overwriting */
1716 if (!force) {
1717 WCHAR* question;
1719 /* Ask for confirmation */
1720 question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1721 ok = WCMD_ask_confirm(question, FALSE, NULL);
1722 LocalFree(question);
1724 /* So delete the destination prior to the move */
1725 if (ok) {
1726 if (!DeleteFileW(dest)) {
1727 WCMD_print_error ();
1728 errorlevel = 1;
1729 ok = FALSE;
1735 if (ok) {
1736 status = MoveFileW(src, dest);
1737 } else {
1738 status = 1; /* Anything other than 0 to prevent error msg below */
1741 if (!status) {
1742 WCMD_print_error ();
1743 errorlevel = 1;
1745 } while (FindNextFileW(hff, &fd) != 0);
1747 FindClose(hff);
1750 /****************************************************************************
1751 * WCMD_pause
1753 * Suspend execution of a batch script until a key is typed
1756 void WCMD_pause (void)
1758 DWORD oldmode;
1759 BOOL have_console;
1760 DWORD count;
1761 WCHAR key;
1762 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1764 have_console = GetConsoleMode(hIn, &oldmode);
1765 if (have_console)
1766 SetConsoleMode(hIn, 0);
1768 WCMD_output_asis(anykey);
1769 WCMD_ReadFile(hIn, &key, 1, &count);
1770 if (have_console)
1771 SetConsoleMode(hIn, oldmode);
1774 /****************************************************************************
1775 * WCMD_remove_dir
1777 * Delete a directory.
1780 void WCMD_remove_dir (WCHAR *command) {
1782 int argno = 0;
1783 int argsProcessed = 0;
1784 WCHAR *argN = command;
1785 static const WCHAR parmS[] = {'/','S','\0'};
1786 static const WCHAR parmQ[] = {'/','Q','\0'};
1788 /* Loop through all args */
1789 while (argN) {
1790 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1791 if (argN && argN[0] != '/') {
1792 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1793 wine_dbgstr_w(quals));
1794 argsProcessed++;
1796 /* If subdirectory search not supplied, just try to remove
1797 and report error if it fails (eg if it contains a file) */
1798 if (strstrW (quals, parmS) == NULL) {
1799 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1801 /* Otherwise use ShFileOp to recursively remove a directory */
1802 } else {
1804 SHFILEOPSTRUCTW lpDir;
1806 /* Ask first */
1807 if (strstrW (quals, parmQ) == NULL) {
1808 BOOL ok;
1809 WCHAR question[MAXSTRING];
1810 static const WCHAR fmt[] = {'%','s',' ','\0'};
1812 /* Ask for confirmation */
1813 wsprintfW(question, fmt, thisArg);
1814 ok = WCMD_ask_confirm(question, TRUE, NULL);
1816 /* Abort if answer is 'N' */
1817 if (!ok) return;
1820 /* Do the delete */
1821 lpDir.hwnd = NULL;
1822 lpDir.pTo = NULL;
1823 lpDir.pFrom = thisArg;
1824 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1825 lpDir.wFunc = FO_DELETE;
1826 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1831 /* Handle no valid args */
1832 if (argsProcessed == 0) {
1833 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1834 return;
1839 /****************************************************************************
1840 * WCMD_rename
1842 * Rename a file.
1845 void WCMD_rename (void)
1847 int status;
1848 HANDLE hff;
1849 WIN32_FIND_DATAW fd;
1850 WCHAR input[MAX_PATH];
1851 WCHAR *dotDst = NULL;
1852 WCHAR drive[10];
1853 WCHAR dir[MAX_PATH];
1854 WCHAR fname[MAX_PATH];
1855 WCHAR ext[MAX_PATH];
1857 errorlevel = 0;
1859 /* Must be at least two args */
1860 if (param1[0] == 0x00 || param2[0] == 0x00) {
1861 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1862 errorlevel = 1;
1863 return;
1866 /* Destination cannot contain a drive letter or directory separator */
1867 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1868 SetLastError(ERROR_INVALID_PARAMETER);
1869 WCMD_print_error();
1870 errorlevel = 1;
1871 return;
1874 /* Convert partial path to full path */
1875 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1876 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1877 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1878 dotDst = strchrW(param2, '.');
1880 /* Split into components */
1881 WCMD_splitpath(input, drive, dir, fname, ext);
1883 hff = FindFirstFileW(input, &fd);
1884 if (hff == INVALID_HANDLE_VALUE)
1885 return;
1887 do {
1888 WCHAR dest[MAX_PATH];
1889 WCHAR src[MAX_PATH];
1890 WCHAR *dotSrc = NULL;
1891 int dirLen;
1893 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1895 /* FIXME: If dest name or extension is *, replace with filename/ext
1896 part otherwise use supplied name. This supports:
1897 ren *.fred *.jim
1898 ren jim.* fred.* etc
1899 However, windows has a more complex algorithm supporting eg
1900 ?'s and *'s mid name */
1901 dotSrc = strchrW(fd.cFileName, '.');
1903 /* Build src & dest name */
1904 strcpyW(src, drive);
1905 strcatW(src, dir);
1906 strcpyW(dest, src);
1907 dirLen = strlenW(src);
1908 strcatW(src, fd.cFileName);
1910 /* Build name */
1911 if (param2[0] == '*') {
1912 strcatW(dest, fd.cFileName);
1913 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1914 } else {
1915 strcatW(dest, param2);
1916 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1919 /* Build Extension */
1920 if (dotDst && (*(dotDst+1)=='*')) {
1921 if (dotSrc) strcatW(dest, dotSrc);
1922 } else if (dotDst) {
1923 if (dotDst) strcatW(dest, dotDst);
1926 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1927 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1929 status = MoveFileW(src, dest);
1931 if (!status) {
1932 WCMD_print_error ();
1933 errorlevel = 1;
1935 } while (FindNextFileW(hff, &fd) != 0);
1937 FindClose(hff);
1940 /*****************************************************************************
1941 * WCMD_dupenv
1943 * Make a copy of the environment.
1945 static WCHAR *WCMD_dupenv( const WCHAR *env )
1947 WCHAR *env_copy;
1948 int len;
1950 if( !env )
1951 return NULL;
1953 len = 0;
1954 while ( env[len] )
1955 len += (strlenW(&env[len]) + 1);
1957 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1958 if (!env_copy)
1960 WINE_ERR("out of memory\n");
1961 return env_copy;
1963 memcpy (env_copy, env, len*sizeof (WCHAR));
1964 env_copy[len] = 0;
1966 return env_copy;
1969 /*****************************************************************************
1970 * WCMD_setlocal
1972 * setlocal pushes the environment onto a stack
1973 * Save the environment as unicode so we don't screw anything up.
1975 void WCMD_setlocal (const WCHAR *s) {
1976 WCHAR *env;
1977 struct env_stack *env_copy;
1978 WCHAR cwd[MAX_PATH];
1980 /* DISABLEEXTENSIONS ignored */
1982 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1983 if( !env_copy )
1985 WINE_ERR ("out of memory\n");
1986 return;
1989 env = GetEnvironmentStringsW ();
1991 env_copy->strings = WCMD_dupenv (env);
1992 if (env_copy->strings)
1994 env_copy->next = saved_environment;
1995 saved_environment = env_copy;
1997 /* Save the current drive letter */
1998 GetCurrentDirectoryW(MAX_PATH, cwd);
1999 env_copy->u.cwd = cwd[0];
2001 else
2002 LocalFree (env_copy);
2004 FreeEnvironmentStringsW (env);
2008 /*****************************************************************************
2009 * WCMD_endlocal
2011 * endlocal pops the environment off a stack
2012 * Note: When searching for '=', search from WCHAR position 1, to handle
2013 * special internal environment variables =C:, =D: etc
2015 void WCMD_endlocal (void) {
2016 WCHAR *env, *old, *p;
2017 struct env_stack *temp;
2018 int len, n;
2020 if (!saved_environment)
2021 return;
2023 /* pop the old environment from the stack */
2024 temp = saved_environment;
2025 saved_environment = temp->next;
2027 /* delete the current environment, totally */
2028 env = GetEnvironmentStringsW ();
2029 old = WCMD_dupenv (GetEnvironmentStringsW ());
2030 len = 0;
2031 while (old[len]) {
2032 n = strlenW(&old[len]) + 1;
2033 p = strchrW(&old[len] + 1, '=');
2034 if (p)
2036 *p++ = 0;
2037 SetEnvironmentVariableW (&old[len], NULL);
2039 len += n;
2041 LocalFree (old);
2042 FreeEnvironmentStringsW (env);
2044 /* restore old environment */
2045 env = temp->strings;
2046 len = 0;
2047 while (env[len]) {
2048 n = strlenW(&env[len]) + 1;
2049 p = strchrW(&env[len] + 1, '=');
2050 if (p)
2052 *p++ = 0;
2053 SetEnvironmentVariableW (&env[len], p);
2055 len += n;
2058 /* Restore current drive letter */
2059 if (IsCharAlphaW(temp->u.cwd)) {
2060 WCHAR envvar[4];
2061 WCHAR cwd[MAX_PATH];
2062 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2064 wsprintfW(envvar, fmt, temp->u.cwd);
2065 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2066 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2067 SetCurrentDirectoryW(cwd);
2071 LocalFree (env);
2072 LocalFree (temp);
2075 /*****************************************************************************
2076 * WCMD_setshow_default
2078 * Set/Show the current default directory
2081 void WCMD_setshow_default (const WCHAR *command) {
2083 BOOL status;
2084 WCHAR string[1024];
2085 WCHAR cwd[1024];
2086 WCHAR *pos;
2087 WIN32_FIND_DATAW fd;
2088 HANDLE hff;
2089 static const WCHAR parmD[] = {'/','D','\0'};
2091 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2093 /* Skip /D and trailing whitespace if on the front of the command line */
2094 if (CompareStringW(LOCALE_USER_DEFAULT,
2095 NORM_IGNORECASE | SORT_STRINGSORT,
2096 command, 2, parmD, -1) == CSTR_EQUAL) {
2097 command += 2;
2098 while (*command && (*command==' ' || *command=='\t'))
2099 command++;
2102 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2103 if (strlenW(command) == 0) {
2104 strcatW (cwd, newlineW);
2105 WCMD_output_asis (cwd);
2107 else {
2108 /* Remove any double quotes, which may be in the
2109 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2110 pos = string;
2111 while (*command) {
2112 if (*command != '"') *pos++ = *command;
2113 command++;
2115 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2116 pos--;
2117 *pos = 0x00;
2119 /* Search for appropriate directory */
2120 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2121 hff = FindFirstFileW(string, &fd);
2122 if (hff != INVALID_HANDLE_VALUE) {
2123 do {
2124 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2125 WCHAR fpath[MAX_PATH];
2126 WCHAR drive[10];
2127 WCHAR dir[MAX_PATH];
2128 WCHAR fname[MAX_PATH];
2129 WCHAR ext[MAX_PATH];
2130 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2132 /* Convert path into actual directory spec */
2133 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2134 WCMD_splitpath(fpath, drive, dir, fname, ext);
2136 /* Rebuild path */
2137 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2138 break;
2140 } while (FindNextFileW(hff, &fd) != 0);
2141 FindClose(hff);
2144 /* Change to that directory */
2145 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2147 status = SetCurrentDirectoryW(string);
2148 if (!status) {
2149 errorlevel = 1;
2150 WCMD_print_error ();
2151 return;
2152 } else {
2154 /* Save away the actual new directory, to store as current location */
2155 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2157 /* Restore old directory if drive letter would change, and
2158 CD x:\directory /D (or pushd c:\directory) not supplied */
2159 if ((strstrW(quals, parmD) == NULL) &&
2160 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2161 SetCurrentDirectoryW(cwd);
2165 /* Set special =C: type environment variable, for drive letter of
2166 change of directory, even if path was restored due to missing
2167 /D (allows changing drive letter when not resident on that
2168 drive */
2169 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2170 WCHAR env[4];
2171 strcpyW(env, equalW);
2172 memcpy(env+1, string, 2 * sizeof(WCHAR));
2173 env[3] = 0x00;
2174 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2175 SetEnvironmentVariableW(env, string);
2179 return;
2182 /****************************************************************************
2183 * WCMD_setshow_date
2185 * Set/Show the system date
2186 * FIXME: Can't change date yet
2189 void WCMD_setshow_date (void) {
2191 WCHAR curdate[64], buffer[64];
2192 DWORD count;
2193 static const WCHAR parmT[] = {'/','T','\0'};
2195 if (strlenW(param1) == 0) {
2196 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2197 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2198 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2199 if (strstrW (quals, parmT) == NULL) {
2200 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2201 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2202 if (count > 2) {
2203 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2207 else WCMD_print_error ();
2209 else {
2210 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2214 /****************************************************************************
2215 * WCMD_compare
2217 static int WCMD_compare( const void *a, const void *b )
2219 int r;
2220 const WCHAR * const *str_a = a, * const *str_b = b;
2221 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2222 *str_a, -1, *str_b, -1 );
2223 if( r == CSTR_LESS_THAN ) return -1;
2224 if( r == CSTR_GREATER_THAN ) return 1;
2225 return 0;
2228 /****************************************************************************
2229 * WCMD_setshow_sortenv
2231 * sort variables into order for display
2232 * Optionally only display those who start with a stub
2233 * returns the count displayed
2235 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2237 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2238 const WCHAR **str;
2240 if (stub) stublen = strlenW(stub);
2242 /* count the number of strings, and the total length */
2243 while ( s[len] ) {
2244 len += (strlenW(&s[len]) + 1);
2245 count++;
2248 /* add the strings to an array */
2249 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2250 if( !str )
2251 return 0;
2252 str[0] = s;
2253 for( i=1; i<count; i++ )
2254 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2256 /* sort the array */
2257 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2259 /* print it */
2260 for( i=0; i<count; i++ ) {
2261 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2262 NORM_IGNORECASE | SORT_STRINGSORT,
2263 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2264 /* Don't display special internal variables */
2265 if (str[i][0] != '=') {
2266 WCMD_output_asis(str[i]);
2267 WCMD_output_asis(newlineW);
2268 displayedcount++;
2273 LocalFree( str );
2274 return displayedcount;
2277 /****************************************************************************
2278 * WCMD_setshow_env
2280 * Set/Show the environment variables
2283 void WCMD_setshow_env (WCHAR *s) {
2285 LPVOID env;
2286 WCHAR *p;
2287 int status;
2288 static const WCHAR parmP[] = {'/','P','\0'};
2290 if (param1[0] == 0x00 && quals[0] == 0x00) {
2291 env = GetEnvironmentStringsW();
2292 WCMD_setshow_sortenv( env, NULL );
2293 return;
2296 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2297 if (CompareStringW(LOCALE_USER_DEFAULT,
2298 NORM_IGNORECASE | SORT_STRINGSORT,
2299 s, 2, parmP, -1) == CSTR_EQUAL) {
2300 WCHAR string[MAXSTRING];
2301 DWORD count;
2303 s += 2;
2304 while (*s && (*s==' ' || *s=='\t')) s++;
2305 if (*s=='\"')
2306 WCMD_strip_quotes(s);
2308 /* If no parameter, or no '=' sign, return an error */
2309 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2310 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2311 return;
2314 /* Output the prompt */
2315 *p++ = '\0';
2316 if (strlenW(p) != 0) WCMD_output_asis(p);
2318 /* Read the reply */
2319 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2320 if (count > 1) {
2321 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2322 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2323 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2324 wine_dbgstr_w(string));
2325 status = SetEnvironmentVariableW(s, string);
2328 } else {
2329 DWORD gle;
2331 if (*s=='\"')
2332 WCMD_strip_quotes(s);
2333 p = strchrW (s, '=');
2334 if (p == NULL) {
2335 env = GetEnvironmentStringsW();
2336 if (WCMD_setshow_sortenv( env, s ) == 0) {
2337 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2338 errorlevel = 1;
2340 return;
2342 *p++ = '\0';
2344 if (strlenW(p) == 0) p = NULL;
2345 status = SetEnvironmentVariableW(s, p);
2346 gle = GetLastError();
2347 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2348 errorlevel = 1;
2349 } else if ((!status)) WCMD_print_error();
2353 /****************************************************************************
2354 * WCMD_setshow_path
2356 * Set/Show the path environment variable
2359 void WCMD_setshow_path (const WCHAR *command) {
2361 WCHAR string[1024];
2362 DWORD status;
2363 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2364 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2366 if (strlenW(param1) == 0) {
2367 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2368 if (status != 0) {
2369 WCMD_output_asis ( pathEqW);
2370 WCMD_output_asis ( string);
2371 WCMD_output_asis ( newlineW);
2373 else {
2374 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2377 else {
2378 if (*command == '=') command++; /* Skip leading '=' */
2379 status = SetEnvironmentVariableW(pathW, command);
2380 if (!status) WCMD_print_error();
2384 /****************************************************************************
2385 * WCMD_setshow_prompt
2387 * Set or show the command prompt.
2390 void WCMD_setshow_prompt (void) {
2392 WCHAR *s;
2393 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2395 if (strlenW(param1) == 0) {
2396 SetEnvironmentVariableW(promptW, NULL);
2398 else {
2399 s = param1;
2400 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2401 if (strlenW(s) == 0) {
2402 SetEnvironmentVariableW(promptW, NULL);
2404 else SetEnvironmentVariableW(promptW, s);
2408 /****************************************************************************
2409 * WCMD_setshow_time
2411 * Set/Show the system time
2412 * FIXME: Can't change time yet
2415 void WCMD_setshow_time (void) {
2417 WCHAR curtime[64], buffer[64];
2418 DWORD count;
2419 SYSTEMTIME st;
2420 static const WCHAR parmT[] = {'/','T','\0'};
2422 if (strlenW(param1) == 0) {
2423 GetLocalTime(&st);
2424 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2425 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2426 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2427 if (strstrW (quals, parmT) == NULL) {
2428 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2429 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2430 if (count > 2) {
2431 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2435 else WCMD_print_error ();
2437 else {
2438 WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
2442 /****************************************************************************
2443 * WCMD_shift
2445 * Shift batch parameters.
2446 * Optional /n says where to start shifting (n=0-8)
2449 void WCMD_shift (const WCHAR *command) {
2450 int start;
2452 if (context != NULL) {
2453 WCHAR *pos = strchrW(command, '/');
2454 int i;
2456 if (pos == NULL) {
2457 start = 0;
2458 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2459 start = (*(pos+1) - '0');
2460 } else {
2461 SetLastError(ERROR_INVALID_PARAMETER);
2462 WCMD_print_error();
2463 return;
2466 WINE_TRACE("Shifting variables, starting at %d\n", start);
2467 for (i=start;i<=8;i++) {
2468 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2470 context -> shift_count[9] = context -> shift_count[9] + 1;
2475 /****************************************************************************
2476 * WCMD_start
2478 void WCMD_start(const WCHAR *command)
2480 static const WCHAR exeW[] = {'\\','c','o','m','m','a','n','d',
2481 '\\','s','t','a','r','t','.','e','x','e',0};
2482 WCHAR file[MAX_PATH];
2483 WCHAR *cmdline;
2484 STARTUPINFOW st;
2485 PROCESS_INFORMATION pi;
2487 GetWindowsDirectoryW( file, MAX_PATH );
2488 strcatW( file, exeW );
2489 cmdline = HeapAlloc( GetProcessHeap(), 0, (strlenW(file) + strlenW(command) + 2) * sizeof(WCHAR) );
2490 strcpyW( cmdline, file );
2491 strcatW( cmdline, spaceW );
2492 strcatW( cmdline, command );
2494 memset( &st, 0, sizeof(STARTUPINFOW) );
2495 st.cb = sizeof(STARTUPINFOW);
2497 if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
2499 WaitForSingleObject( pi.hProcess, INFINITE );
2500 GetExitCodeProcess( pi.hProcess, &errorlevel );
2501 if (errorlevel == STILL_ACTIVE) errorlevel = 0;
2502 CloseHandle(pi.hProcess);
2503 CloseHandle(pi.hThread);
2505 else
2507 SetLastError(ERROR_FILE_NOT_FOUND);
2508 WCMD_print_error ();
2509 errorlevel = 9009;
2511 HeapFree( GetProcessHeap(), 0, cmdline );
2514 /****************************************************************************
2515 * WCMD_title
2517 * Set the console title
2519 void WCMD_title (const WCHAR *command) {
2520 SetConsoleTitleW(command);
2523 /****************************************************************************
2524 * WCMD_type
2526 * Copy a file to standard output.
2529 void WCMD_type (WCHAR *command) {
2531 int argno = 0;
2532 WCHAR *argN = command;
2533 BOOL writeHeaders = FALSE;
2535 if (param1[0] == 0x00) {
2536 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2537 return;
2540 if (param2[0] != 0x00) writeHeaders = TRUE;
2542 /* Loop through all args */
2543 errorlevel = 0;
2544 while (argN) {
2545 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2547 HANDLE h;
2548 WCHAR buffer[512];
2549 DWORD count;
2551 if (!argN) break;
2553 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2554 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2555 FILE_ATTRIBUTE_NORMAL, NULL);
2556 if (h == INVALID_HANDLE_VALUE) {
2557 WCMD_print_error ();
2558 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2559 errorlevel = 1;
2560 } else {
2561 if (writeHeaders) {
2562 static const WCHAR fmt[] = {'\n','%','1','\n','\n','\0'};
2563 WCMD_output(fmt, thisArg);
2565 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2566 if (count == 0) break; /* ReadFile reports success on EOF! */
2567 buffer[count] = 0;
2568 WCMD_output_asis (buffer);
2570 CloseHandle (h);
2575 /****************************************************************************
2576 * WCMD_more
2578 * Output either a file or stdin to screen in pages
2581 void WCMD_more (WCHAR *command) {
2583 int argno = 0;
2584 WCHAR *argN = command;
2585 WCHAR moreStr[100];
2586 WCHAR moreStrPage[100];
2587 WCHAR buffer[512];
2588 DWORD count;
2589 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2590 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2591 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2592 ')',' ','-','-','\n','\0'};
2593 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2595 /* Prefix the NLS more with '-- ', then load the text */
2596 errorlevel = 0;
2597 strcpyW(moreStr, moreStart);
2598 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2599 (sizeof(moreStr)/sizeof(WCHAR))-3);
2601 if (param1[0] == 0x00) {
2603 /* Wine implements pipes via temporary files, and hence stdin is
2604 effectively reading from the file. This means the prompts for
2605 more are satisfied by the next line from the input (file). To
2606 avoid this, ensure stdin is to the console */
2607 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2608 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2609 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2610 FILE_ATTRIBUTE_NORMAL, 0);
2611 WINE_TRACE("No parms - working probably in pipe mode\n");
2612 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2614 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2615 once you get in this bit unless due to a pipe, its going to end badly... */
2616 wsprintfW(moreStrPage, moreFmt, moreStr);
2618 WCMD_enter_paged_mode(moreStrPage);
2619 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2620 if (count == 0) break; /* ReadFile reports success on EOF! */
2621 buffer[count] = 0;
2622 WCMD_output_asis (buffer);
2624 WCMD_leave_paged_mode();
2626 /* Restore stdin to what it was */
2627 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2628 CloseHandle(hConIn);
2630 return;
2631 } else {
2632 BOOL needsPause = FALSE;
2634 /* Loop through all args */
2635 WINE_TRACE("Parms supplied - working through each file\n");
2636 WCMD_enter_paged_mode(moreStrPage);
2638 while (argN) {
2639 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2640 HANDLE h;
2642 if (!argN) break;
2644 if (needsPause) {
2646 /* Wait */
2647 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2648 WCMD_leave_paged_mode();
2649 WCMD_output_asis(moreStrPage);
2650 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2651 WCMD_enter_paged_mode(moreStrPage);
2655 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2656 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2657 FILE_ATTRIBUTE_NORMAL, NULL);
2658 if (h == INVALID_HANDLE_VALUE) {
2659 WCMD_print_error ();
2660 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2661 errorlevel = 1;
2662 } else {
2663 ULONG64 curPos = 0;
2664 ULONG64 fileLen = 0;
2665 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2667 /* Get the file size */
2668 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2669 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2671 needsPause = TRUE;
2672 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2673 if (count == 0) break; /* ReadFile reports success on EOF! */
2674 buffer[count] = 0;
2675 curPos += count;
2677 /* Update % count (would be used in WCMD_output_asis as prompt) */
2678 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2680 WCMD_output_asis (buffer);
2682 CloseHandle (h);
2686 WCMD_leave_paged_mode();
2690 /****************************************************************************
2691 * WCMD_verify
2693 * Display verify flag.
2694 * FIXME: We don't actually do anything with the verify flag other than toggle
2695 * it...
2698 void WCMD_verify (const WCHAR *command) {
2700 int count;
2702 count = strlenW(command);
2703 if (count == 0) {
2704 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2705 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2706 return;
2708 if (lstrcmpiW(command, onW) == 0) {
2709 verify_mode = TRUE;
2710 return;
2712 else if (lstrcmpiW(command, offW) == 0) {
2713 verify_mode = FALSE;
2714 return;
2716 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2719 /****************************************************************************
2720 * WCMD_version
2722 * Display version info.
2725 void WCMD_version (void) {
2727 WCMD_output_asis (version_string);
2731 /****************************************************************************
2732 * WCMD_volume
2734 * Display volume information (set_label = FALSE)
2735 * Additionally set volume label (set_label = TRUE)
2736 * Returns 1 on success, 0 otherwise
2739 int WCMD_volume(BOOL set_label, const WCHAR *path)
2741 DWORD count, serial;
2742 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2743 BOOL status;
2745 if (strlenW(path) == 0) {
2746 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2747 if (!status) {
2748 WCMD_print_error ();
2749 return 0;
2751 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2752 &serial, NULL, NULL, NULL, 0);
2754 else {
2755 static const WCHAR fmt[] = {'%','s','\\','\0'};
2756 if ((path[1] != ':') || (strlenW(path) != 2)) {
2757 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2758 return 0;
2760 wsprintfW (curdir, fmt, path);
2761 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2762 &serial, NULL,
2763 NULL, NULL, 0);
2765 if (!status) {
2766 WCMD_print_error ();
2767 return 0;
2769 if (label[0] != '\0') {
2770 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
2771 curdir[0], label);
2773 else {
2774 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
2775 curdir[0]);
2777 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
2778 HIWORD(serial), LOWORD(serial));
2779 if (set_label) {
2780 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2781 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2782 if (count > 1) {
2783 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2784 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2786 if (strlenW(path) != 0) {
2787 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2789 else {
2790 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2793 return 1;
2796 /**************************************************************************
2797 * WCMD_exit
2799 * Exit either the process, or just this batch program
2803 void WCMD_exit (CMD_LIST **cmdList) {
2805 static const WCHAR parmB[] = {'/','B','\0'};
2806 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2808 if (context && lstrcmpiW(quals, parmB) == 0) {
2809 errorlevel = rc;
2810 context -> skip_rest = TRUE;
2811 *cmdList = NULL;
2812 } else {
2813 ExitProcess(rc);
2818 /*****************************************************************************
2819 * WCMD_assoc
2821 * Lists or sets file associations (assoc = TRUE)
2822 * Lists or sets file types (assoc = FALSE)
2824 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2826 HKEY key;
2827 DWORD accessOptions = KEY_READ;
2828 WCHAR *newValue;
2829 LONG rc = ERROR_SUCCESS;
2830 WCHAR keyValue[MAXSTRING];
2831 DWORD valueLen = MAXSTRING;
2832 HKEY readKey;
2833 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2834 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2836 /* See if parameter includes '=' */
2837 errorlevel = 0;
2838 newValue = strchrW(command, '=');
2839 if (newValue) accessOptions |= KEY_WRITE;
2841 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2842 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2843 accessOptions, &key) != ERROR_SUCCESS) {
2844 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2845 return;
2848 /* If no parameters then list all associations */
2849 if (*command == 0x00) {
2850 int index = 0;
2852 /* Enumerate all the keys */
2853 while (rc != ERROR_NO_MORE_ITEMS) {
2854 WCHAR keyName[MAXSTRING];
2855 DWORD nameLen;
2857 /* Find the next value */
2858 nameLen = MAXSTRING;
2859 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2861 if (rc == ERROR_SUCCESS) {
2863 /* Only interested in extension ones if assoc, or others
2864 if not assoc */
2865 if ((keyName[0] == '.' && assoc) ||
2866 (!(keyName[0] == '.') && (!assoc)))
2868 WCHAR subkey[MAXSTRING];
2869 strcpyW(subkey, keyName);
2870 if (!assoc) strcatW(subkey, shOpCmdW);
2872 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2874 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2875 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2876 WCMD_output_asis(keyName);
2877 WCMD_output_asis(equalW);
2878 /* If no default value found, leave line empty after '=' */
2879 if (rc == ERROR_SUCCESS) {
2880 WCMD_output_asis(keyValue);
2882 WCMD_output_asis(newlineW);
2883 RegCloseKey(readKey);
2889 } else {
2891 /* Parameter supplied - if no '=' on command line, its a query */
2892 if (newValue == NULL) {
2893 WCHAR *space;
2894 WCHAR subkey[MAXSTRING];
2896 /* Query terminates the parameter at the first space */
2897 strcpyW(keyValue, command);
2898 space = strchrW(keyValue, ' ');
2899 if (space) *space=0x00;
2901 /* Set up key name */
2902 strcpyW(subkey, keyValue);
2903 if (!assoc) strcatW(subkey, shOpCmdW);
2905 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2907 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2908 WCMD_output_asis(command);
2909 WCMD_output_asis(equalW);
2910 /* If no default value found, leave line empty after '=' */
2911 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2912 WCMD_output_asis(newlineW);
2913 RegCloseKey(readKey);
2915 } else {
2916 WCHAR msgbuffer[MAXSTRING];
2918 /* Load the translated 'File association not found' */
2919 if (assoc) {
2920 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2921 } else {
2922 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2924 WCMD_output_stderr(msgbuffer, keyValue);
2925 errorlevel = 2;
2928 /* Not a query - its a set or clear of a value */
2929 } else {
2931 WCHAR subkey[MAXSTRING];
2933 /* Get pointer to new value */
2934 *newValue = 0x00;
2935 newValue++;
2937 /* Set up key name */
2938 strcpyW(subkey, command);
2939 if (!assoc) strcatW(subkey, shOpCmdW);
2941 /* If nothing after '=' then clear value - only valid for ASSOC */
2942 if (*newValue == 0x00) {
2944 if (assoc) rc = RegDeleteKeyW(key, command);
2945 if (assoc && rc == ERROR_SUCCESS) {
2946 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2948 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2949 WCMD_print_error();
2950 errorlevel = 2;
2952 } else {
2953 WCHAR msgbuffer[MAXSTRING];
2955 /* Load the translated 'File association not found' */
2956 if (assoc) {
2957 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2958 sizeof(msgbuffer)/sizeof(WCHAR));
2959 } else {
2960 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2961 sizeof(msgbuffer)/sizeof(WCHAR));
2963 WCMD_output_stderr(msgbuffer, keyValue);
2964 errorlevel = 2;
2967 /* It really is a set value = contents */
2968 } else {
2969 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2970 accessOptions, NULL, &readKey, NULL);
2971 if (rc == ERROR_SUCCESS) {
2972 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2973 (LPBYTE)newValue,
2974 sizeof(WCHAR) * (strlenW(newValue) + 1));
2975 RegCloseKey(readKey);
2978 if (rc != ERROR_SUCCESS) {
2979 WCMD_print_error();
2980 errorlevel = 2;
2981 } else {
2982 WCMD_output_asis(command);
2983 WCMD_output_asis(equalW);
2984 WCMD_output_asis(newValue);
2985 WCMD_output_asis(newlineW);
2991 /* Clean up */
2992 RegCloseKey(key);
2995 /****************************************************************************
2996 * WCMD_color
2998 * Colors the terminal screen.
3001 void WCMD_color (void) {
3003 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
3004 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
3006 if (param1[0] != 0x00 && strlenW(param1) > 2) {
3007 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
3008 return;
3011 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
3013 COORD topLeft;
3014 DWORD screenSize;
3015 DWORD color = 0;
3017 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
3019 topLeft.X = 0;
3020 topLeft.Y = 0;
3022 /* Convert the color hex digits */
3023 if (param1[0] == 0x00) {
3024 color = defaultColor;
3025 } else {
3026 color = strtoulW(param1, NULL, 16);
3029 /* Fail if fg == bg color */
3030 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3031 errorlevel = 1;
3032 return;
3035 /* Set the current screen contents and ensure all future writes
3036 remain this color */
3037 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3038 SetConsoleTextAttribute(hStdOut, color);