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