cmd: Make PAUSE accept any keypress instead of a full input line.
[wine.git] / programs / cmd / builtins.c
blob1db9b3cbd60f09bf0f487cdda000691fa188d9f1
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 * NOTES:
24 * On entry to each function, global variables quals, param1, param2 contain
25 * the qualifiers (uppercased and concatenated) and parameters entered, with
26 * environment-variable and batch parameter substitution already done.
30 * FIXME:
31 * - No support for pipes, shell parameters
32 * - Lots of functionality missing from builtins
33 * - Messages etc need international support
36 #define WIN32_LEAN_AND_MEAN
38 #include "wcmd.h"
39 #include <shellapi.h>
40 #include "wine/debug.h"
42 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
44 static void WCMD_part_execute(CMD_LIST **commands, const WCHAR *firstcmd,
45 const WCHAR *variable, const WCHAR *value,
46 BOOL isIF, BOOL conditionTRUE);
48 static struct env_stack *saved_environment;
49 struct env_stack *pushd_directories;
51 extern HINSTANCE hinst;
52 extern WCHAR inbuilt[][10];
53 extern int defaultColor;
54 extern BOOL echo_mode;
55 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
56 extern BATCH_CONTEXT *context;
57 extern DWORD errorlevel;
59 static BOOL verify_mode = FALSE;
61 static const WCHAR dotW[] = {'.','\0'};
62 static const WCHAR dotdotW[] = {'.','.','\0'};
63 static const WCHAR slashW[] = {'\\','\0'};
64 static const WCHAR starW[] = {'*','\0'};
65 static const WCHAR equalW[] = {'=','\0'};
66 static const WCHAR fslashW[] = {'/','\0'};
67 static const WCHAR onW[] = {'O','N','\0'};
68 static const WCHAR offW[] = {'O','F','F','\0'};
69 static const WCHAR parmY[] = {'/','Y','\0'};
70 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
71 static const WCHAR nullW[] = {'\0'};
73 /**************************************************************************
74 * WCMD_ask_confirm
76 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
77 * answer.
79 * Returns True if Y (or A) answer is selected
80 * If optionAll contains a pointer, ALL is allowed, and if answered
81 * set to TRUE
84 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
85 const BOOL *optionAll) {
87 WCHAR msgbuffer[MAXSTRING];
88 WCHAR Ybuffer[MAXSTRING];
89 WCHAR Nbuffer[MAXSTRING];
90 WCHAR Abuffer[MAXSTRING];
91 WCHAR answer[MAX_PATH] = {'\0'};
92 DWORD count = 0;
94 /* Load the translated 'Are you sure', plus valid answers */
95 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
96 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
97 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
98 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
100 /* Loop waiting on a Y or N */
101 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
102 static const WCHAR startBkt[] = {' ','(','\0'};
103 static const WCHAR endBkt[] = {')','?','\0'};
105 WCMD_output_asis (message);
106 if (showSureText) {
107 WCMD_output_asis (msgbuffer);
109 WCMD_output_asis (startBkt);
110 WCMD_output_asis (Ybuffer);
111 WCMD_output_asis (fslashW);
112 WCMD_output_asis (Nbuffer);
113 if (optionAll) {
114 WCMD_output_asis (fslashW);
115 WCMD_output_asis (Abuffer);
117 WCMD_output_asis (endBkt);
118 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
119 answer[0] = toupperW(answer[0]);
122 /* Return the answer */
123 return ((answer[0] == Ybuffer[0]) ||
124 (optionAll && (answer[0] == Abuffer[0])));
127 /****************************************************************************
128 * WCMD_clear_screen
130 * Clear the terminal screen.
133 void WCMD_clear_screen (void) {
135 /* Emulate by filling the screen from the top left to bottom right with
136 spaces, then moving the cursor to the top left afterwards */
137 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
138 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
140 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
142 COORD topLeft;
143 DWORD screenSize;
145 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
147 topLeft.X = 0;
148 topLeft.Y = 0;
149 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
150 SetConsoleCursorPosition(hStdOut, topLeft);
154 /****************************************************************************
155 * WCMD_change_tty
157 * Change the default i/o device (ie redirect STDin/STDout).
160 void WCMD_change_tty (void) {
162 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
166 /****************************************************************************
167 * WCMD_choice
171 void WCMD_choice (const WCHAR * command) {
173 static const WCHAR bellW[] = {7,0};
174 static const WCHAR commaW[] = {',',0};
175 static const WCHAR bracket_open[] = {'[',0};
176 static const WCHAR bracket_close[] = {']','?',0};
177 WCHAR answer[16];
178 WCHAR buffer[16];
179 WCHAR *ptr = NULL;
180 WCHAR *opt_c = NULL;
181 WCHAR *my_command = NULL;
182 WCHAR opt_default = 0;
183 DWORD opt_timeout = 0;
184 DWORD count;
185 DWORD oldmode;
186 DWORD have_console;
187 BOOL opt_n = FALSE;
188 BOOL opt_s = FALSE;
190 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
191 errorlevel = 0;
193 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
194 if (!my_command)
195 return;
197 ptr = WCMD_skip_leading_spaces(my_command);
198 while (*ptr == '/') {
199 switch (toupperW(ptr[1])) {
200 case 'C':
201 ptr += 2;
202 /* the colon is optional */
203 if (*ptr == ':')
204 ptr++;
206 if (!*ptr || isspaceW(*ptr)) {
207 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
208 HeapFree(GetProcessHeap(), 0, my_command);
209 return;
212 /* remember the allowed keys (overwrite previous /C option) */
213 opt_c = ptr;
214 while (*ptr && (!isspaceW(*ptr)))
215 ptr++;
217 if (*ptr) {
218 /* terminate allowed chars */
219 *ptr = 0;
220 ptr = WCMD_skip_leading_spaces(&ptr[1]);
222 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
223 break;
225 case 'N':
226 opt_n = TRUE;
227 ptr = WCMD_skip_leading_spaces(&ptr[2]);
228 break;
230 case 'S':
231 opt_s = TRUE;
232 ptr = WCMD_skip_leading_spaces(&ptr[2]);
233 break;
235 case 'T':
236 ptr = &ptr[2];
237 /* the colon is optional */
238 if (*ptr == ':')
239 ptr++;
241 opt_default = *ptr++;
243 if (!opt_default || (*ptr != ',')) {
244 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
245 HeapFree(GetProcessHeap(), 0, my_command);
246 return;
248 ptr++;
250 count = 0;
251 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
252 count++;
253 ptr++;
256 answer[count] = 0;
257 opt_timeout = atoiW(answer);
259 ptr = WCMD_skip_leading_spaces(ptr);
260 break;
262 default:
263 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
264 HeapFree(GetProcessHeap(), 0, my_command);
265 return;
269 if (opt_timeout)
270 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
272 if (have_console)
273 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
275 /* use default keys, when needed: localized versions of "Y"es and "No" */
276 if (!opt_c) {
277 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
278 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
279 opt_c = buffer;
280 buffer[2] = 0;
283 /* print the question, when needed */
284 if (*ptr)
285 WCMD_output_asis(ptr);
287 if (!opt_s) {
288 struprW(opt_c);
289 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
292 if (!opt_n) {
293 /* print a list of all allowed answers inside brackets */
294 WCMD_output_asis(bracket_open);
295 ptr = opt_c;
296 answer[1] = 0;
297 while ((answer[0] = *ptr++)) {
298 WCMD_output_asis(answer);
299 if (*ptr)
300 WCMD_output_asis(commaW);
302 WCMD_output_asis(bracket_close);
305 while (TRUE) {
307 /* FIXME: Add support for option /T */
308 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
310 if (!opt_s)
311 answer[0] = toupperW(answer[0]);
313 ptr = strchrW(opt_c, answer[0]);
314 if (ptr) {
315 WCMD_output_asis(answer);
316 WCMD_output(newline);
317 if (have_console)
318 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
320 errorlevel = (ptr - opt_c) + 1;
321 WINE_TRACE("answer: %d\n", errorlevel);
322 HeapFree(GetProcessHeap(), 0, my_command);
323 return;
325 else
327 /* key not allowed: play the bell */
328 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
329 WCMD_output_asis(bellW);
334 /****************************************************************************
335 * WCMD_copy
337 * Copy a file or wildcarded set.
338 * FIXME: Add support for a+b+c type syntax
341 void WCMD_copy (void) {
343 WIN32_FIND_DATAW fd;
344 HANDLE hff;
345 BOOL force, status;
346 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
347 DWORD len;
348 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
349 BOOL copyToDir = FALSE;
350 WCHAR srcspec[MAX_PATH];
351 DWORD attribs;
352 WCHAR drive[10];
353 WCHAR dir[MAX_PATH];
354 WCHAR fname[MAX_PATH];
355 WCHAR ext[MAX_PATH];
357 if (param1[0] == 0x00) {
358 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
359 return;
362 /* Convert source into full spec */
363 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
364 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
365 if (srcpath[strlenW(srcpath) - 1] == '\\')
366 srcpath[strlenW(srcpath) - 1] = '\0';
368 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
369 attribs = GetFileAttributesW(srcpath);
370 } else {
371 attribs = 0;
373 strcpyW(srcspec, srcpath);
375 /* If a directory, then add \* on the end when searching */
376 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
377 strcatW(srcpath, slashW);
378 strcatW(srcspec, slashW);
379 strcatW(srcspec, starW);
380 } else {
381 WCMD_splitpath(srcpath, drive, dir, fname, ext);
382 strcpyW(srcpath, drive);
383 strcatW(srcpath, dir);
386 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
388 /* If no destination supplied, assume current directory */
389 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
390 if (param2[0] == 0x00) {
391 strcpyW(param2, dotW);
394 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
395 if (outpath[strlenW(outpath) - 1] == '\\')
396 outpath[strlenW(outpath) - 1] = '\0';
397 attribs = GetFileAttributesW(outpath);
398 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
399 strcatW (outpath, slashW);
400 copyToDir = TRUE;
402 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
403 wine_dbgstr_w(outpath), copyToDir);
405 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
406 if (strstrW (quals, parmNoY))
407 force = FALSE;
408 else if (strstrW (quals, parmY))
409 force = TRUE;
410 else {
411 /* By default, we will force the overwrite in batch mode and ask for
412 * confirmation in interactive mode. */
413 force = !!context;
415 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
416 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
417 * default behavior. */
418 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
419 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
420 if (!lstrcmpiW (copycmd, parmY))
421 force = TRUE;
422 else if (!lstrcmpiW (copycmd, parmNoY))
423 force = FALSE;
427 /* Loop through all source files */
428 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
429 hff = FindFirstFileW(srcspec, &fd);
430 if (hff != INVALID_HANDLE_VALUE) {
431 do {
432 WCHAR outname[MAX_PATH];
433 WCHAR srcname[MAX_PATH];
434 BOOL overwrite = force;
436 /* Destination is either supplied filename, or source name in
437 supplied destination directory */
438 strcpyW(outname, outpath);
439 if (copyToDir) strcatW(outname, fd.cFileName);
440 strcpyW(srcname, srcpath);
441 strcatW(srcname, fd.cFileName);
443 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
444 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
446 /* Skip . and .., and directories */
447 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
448 overwrite = FALSE;
449 WINE_TRACE("Skipping directories\n");
452 /* Prompt before overwriting */
453 else if (!overwrite) {
454 attribs = GetFileAttributesW(outname);
455 if (attribs != INVALID_FILE_ATTRIBUTES) {
456 WCHAR buffer[MAXSTRING];
457 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
458 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
460 else overwrite = TRUE;
463 /* Do the copy as appropriate */
464 if (overwrite) {
465 status = CopyFileW(srcname, outname, FALSE);
466 if (!status) WCMD_print_error ();
469 } while (FindNextFileW(hff, &fd) != 0);
470 FindClose (hff);
471 } else {
472 status = ERROR_FILE_NOT_FOUND;
473 WCMD_print_error ();
477 /****************************************************************************
478 * WCMD_create_dir
480 * Create a directory (and, if needed, any intermediate directories).
482 * Modifies its argument by replacing slashes temporarily with nulls.
485 static BOOL create_full_path(WCHAR* path)
487 WCHAR *p, *start;
489 /* don't mess with drive letter portion of path, if any */
490 start = path;
491 if (path[1] == ':')
492 start = path+2;
494 /* Strip trailing slashes. */
495 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
496 *p = 0;
498 /* Step through path, creating intermediate directories as needed. */
499 /* First component includes drive letter, if any. */
500 p = start;
501 for (;;) {
502 DWORD rv;
503 /* Skip to end of component */
504 while (*p == '\\') p++;
505 while (*p && *p != '\\') p++;
506 if (!*p) {
507 /* path is now the original full path */
508 return CreateDirectoryW(path, NULL);
510 /* Truncate path, create intermediate directory, and restore path */
511 *p = 0;
512 rv = CreateDirectoryW(path, NULL);
513 *p = '\\';
514 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
515 return FALSE;
517 /* notreached */
518 return FALSE;
521 void WCMD_create_dir (WCHAR *command) {
522 int argno = 0;
523 WCHAR *argN = command;
525 if (param1[0] == 0x00) {
526 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
527 return;
529 /* Loop through all args */
530 while (TRUE) {
531 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL);
532 if (!argN) break;
533 if (!create_full_path(thisArg)) {
534 WCMD_print_error ();
535 errorlevel = 1;
540 /* Parse the /A options given by the user on the commandline
541 * into a bitmask of wanted attributes (*wantSet),
542 * and a bitmask of unwanted attributes (*wantClear).
544 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
545 static const WCHAR parmA[] = {'/','A','\0'};
546 WCHAR *p;
548 /* both are strictly 'out' parameters */
549 *wantSet=0;
550 *wantClear=0;
552 /* For each /A argument */
553 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
554 /* Skip /A itself */
555 p += 2;
557 /* Skip optional : */
558 if (*p == ':') p++;
560 /* For each of the attribute specifier chars to this /A option */
561 for (; *p != 0 && *p != '/'; p++) {
562 BOOL negate = FALSE;
563 DWORD mask = 0;
565 if (*p == '-') {
566 negate=TRUE;
567 p++;
570 /* Convert the attribute specifier to a bit in one of the masks */
571 switch (*p) {
572 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
573 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
574 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
575 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
576 default:
577 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
579 if (negate)
580 *wantClear |= mask;
581 else
582 *wantSet |= mask;
587 /* If filename part of parameter is * or *.*,
588 * and neither /Q nor /P options were given,
589 * prompt the user whether to proceed.
590 * Returns FALSE if user says no, TRUE otherwise.
591 * *pPrompted is set to TRUE if the user is prompted.
592 * (If /P supplied, del will prompt for individual files later.)
594 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
595 static const WCHAR parmP[] = {'/','P','\0'};
596 static const WCHAR parmQ[] = {'/','Q','\0'};
598 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
599 static const WCHAR anyExt[]= {'.','*','\0'};
600 WCHAR drive[10];
601 WCHAR dir[MAX_PATH];
602 WCHAR fname[MAX_PATH];
603 WCHAR ext[MAX_PATH];
604 WCHAR fpath[MAX_PATH];
606 /* Convert path into actual directory spec */
607 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
608 WCMD_splitpath(fpath, drive, dir, fname, ext);
610 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
611 if ((strcmpW(fname, starW) == 0) &&
612 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
614 WCHAR question[MAXSTRING];
615 static const WCHAR fmt[] = {'%','s',' ','\0'};
617 /* Caller uses this to suppress "file not found" warning later */
618 *pPrompted = TRUE;
620 /* Ask for confirmation */
621 wsprintfW(question, fmt, fpath);
622 return WCMD_ask_confirm(question, TRUE, NULL);
625 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
626 return TRUE;
629 /* Helper function for WCMD_delete().
630 * Deletes a single file, directory, or wildcard.
631 * If /S was given, does it recursively.
632 * Returns TRUE if a file was deleted.
634 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
636 static const WCHAR parmP[] = {'/','P','\0'};
637 static const WCHAR parmS[] = {'/','S','\0'};
638 static const WCHAR parmF[] = {'/','F','\0'};
639 DWORD wanted_attrs;
640 DWORD unwanted_attrs;
641 BOOL found = FALSE;
642 WCHAR argCopy[MAX_PATH];
643 WIN32_FIND_DATAW fd;
644 HANDLE hff;
645 WCHAR fpath[MAX_PATH];
646 WCHAR *p;
647 BOOL handleParm = TRUE;
649 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
651 strcpyW(argCopy, thisArg);
652 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
653 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
655 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
656 /* Skip this arg if user declines to delete *.* */
657 return FALSE;
660 /* First, try to delete in the current directory */
661 hff = FindFirstFileW(argCopy, &fd);
662 if (hff == INVALID_HANDLE_VALUE) {
663 handleParm = FALSE;
664 } else {
665 found = TRUE;
668 /* Support del <dirname> by just deleting all files dirname\* */
669 if (handleParm
670 && (strchrW(argCopy,'*') == NULL)
671 && (strchrW(argCopy,'?') == NULL)
672 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
674 WCHAR modifiedParm[MAX_PATH];
675 static const WCHAR slashStar[] = {'\\','*','\0'};
677 strcpyW(modifiedParm, argCopy);
678 strcatW(modifiedParm, slashStar);
679 FindClose(hff);
680 found = TRUE;
681 WCMD_delete_one(modifiedParm);
683 } else if (handleParm) {
685 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
686 strcpyW (fpath, argCopy);
687 do {
688 p = strrchrW (fpath, '\\');
689 if (p != NULL) {
690 *++p = '\0';
691 strcatW (fpath, fd.cFileName);
693 else strcpyW (fpath, fd.cFileName);
694 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
695 BOOL ok;
697 /* Handle attribute matching (/A) */
698 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
699 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
701 /* /P means prompt for each file */
702 if (ok && strstrW (quals, parmP) != NULL) {
703 WCHAR question[MAXSTRING];
705 /* Ask for confirmation */
706 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
707 ok = WCMD_ask_confirm(question, FALSE, NULL);
710 /* Only proceed if ok to */
711 if (ok) {
713 /* If file is read only, and /A:r or /F supplied, delete it */
714 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
715 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
716 strstrW (quals, parmF) != NULL)) {
717 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
720 /* Now do the delete */
721 if (!DeleteFileW(fpath)) WCMD_print_error ();
725 } while (FindNextFileW(hff, &fd) != 0);
726 FindClose (hff);
729 /* Now recurse into all subdirectories handling the parameter in the same way */
730 if (strstrW (quals, parmS) != NULL) {
732 WCHAR thisDir[MAX_PATH];
733 int cPos;
735 WCHAR drive[10];
736 WCHAR dir[MAX_PATH];
737 WCHAR fname[MAX_PATH];
738 WCHAR ext[MAX_PATH];
740 /* Convert path into actual directory spec */
741 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
742 WCMD_splitpath(thisDir, drive, dir, fname, ext);
744 strcpyW(thisDir, drive);
745 strcatW(thisDir, dir);
746 cPos = strlenW(thisDir);
748 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
750 /* Append '*' to the directory */
751 thisDir[cPos] = '*';
752 thisDir[cPos+1] = 0x00;
754 hff = FindFirstFileW(thisDir, &fd);
756 /* Remove residual '*' */
757 thisDir[cPos] = 0x00;
759 if (hff != INVALID_HANDLE_VALUE) {
760 DIRECTORY_STACK *allDirs = NULL;
761 DIRECTORY_STACK *lastEntry = NULL;
763 do {
764 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
765 (strcmpW(fd.cFileName, dotdotW) != 0) &&
766 (strcmpW(fd.cFileName, dotW) != 0)) {
768 DIRECTORY_STACK *nextDir;
769 WCHAR subParm[MAX_PATH];
771 /* Work out search parameter in sub dir */
772 strcpyW (subParm, thisDir);
773 strcatW (subParm, fd.cFileName);
774 strcatW (subParm, slashW);
775 strcatW (subParm, fname);
776 strcatW (subParm, ext);
777 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
779 /* Allocate memory, add to list */
780 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
781 if (allDirs == NULL) allDirs = nextDir;
782 if (lastEntry != NULL) lastEntry->next = nextDir;
783 lastEntry = nextDir;
784 nextDir->next = NULL;
785 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
786 (strlenW(subParm)+1) * sizeof(WCHAR));
787 strcpyW(nextDir->dirName, subParm);
789 } while (FindNextFileW(hff, &fd) != 0);
790 FindClose (hff);
792 /* Go through each subdir doing the delete */
793 while (allDirs != NULL) {
794 DIRECTORY_STACK *tempDir;
796 tempDir = allDirs->next;
797 found |= WCMD_delete_one (allDirs->dirName);
799 HeapFree(GetProcessHeap(),0,allDirs->dirName);
800 HeapFree(GetProcessHeap(),0,allDirs);
801 allDirs = tempDir;
806 return found;
809 /****************************************************************************
810 * WCMD_delete
812 * Delete a file or wildcarded set.
814 * Note on /A:
815 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
816 * - Each set is a pattern, eg /ahr /as-r means
817 * readonly+hidden OR nonreadonly system files
818 * - The '-' applies to a single field, ie /a:-hr means read only
819 * non-hidden files
822 BOOL WCMD_delete (WCHAR *command) {
823 int argno;
824 WCHAR *argN;
825 BOOL argsProcessed = FALSE;
826 BOOL foundAny = FALSE;
828 errorlevel = 0;
830 for (argno=0; ; argno++) {
831 BOOL found;
832 WCHAR *thisArg;
834 argN = NULL;
835 thisArg = WCMD_parameter (command, argno, &argN, NULL);
836 if (!argN)
837 break; /* no more parameters */
838 if (argN[0] == '/')
839 continue; /* skip options */
841 argsProcessed = TRUE;
842 found = WCMD_delete_one(thisArg);
843 if (!found) {
844 errorlevel = 1;
845 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
847 foundAny |= found;
850 /* Handle no valid args */
851 if (!argsProcessed)
852 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
854 return foundAny;
857 /****************************************************************************
858 * WCMD_echo
860 * Echo input to the screen (or not). We don't try to emulate the bugs
861 * in DOS (try typing "ECHO ON AGAIN" for an example).
864 void WCMD_echo (const WCHAR *command) {
866 int count;
867 const WCHAR *origcommand = command;
869 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
870 || command[0]==':' || command[0]==';')
871 command++;
873 count = strlenW(command);
874 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
875 && origcommand[0]!=';') {
876 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
877 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
878 return;
880 if (lstrcmpiW(command, onW) == 0) {
881 echo_mode = TRUE;
882 return;
884 if (lstrcmpiW(command, offW) == 0) {
885 echo_mode = FALSE;
886 return;
888 WCMD_output_asis (command);
889 WCMD_output (newline);
893 /**************************************************************************
894 * WCMD_for
896 * Batch file loop processing.
898 * On entry: cmdList contains the syntax up to the set
899 * next cmdList and all in that bracket contain the set data
900 * next cmdlist contains the DO cmd
901 * following that is either brackets or && entries (as per if)
905 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
907 WIN32_FIND_DATAW fd;
908 HANDLE hff;
909 int i;
910 static const WCHAR inW[] = {'i','n'};
911 static const WCHAR doW[] = {'d','o'};
912 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
913 WCHAR variable[4];
914 WCHAR *firstCmd;
915 int thisDepth;
917 WCHAR *curPos = p;
918 BOOL expandDirs = FALSE;
919 BOOL useNumbers = FALSE;
920 BOOL doFileset = FALSE;
921 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
922 int itemNum;
923 CMD_LIST *thisCmdStart;
926 /* Handle optional qualifiers (multiple are allowed) */
927 while (*curPos && *curPos == '/') {
928 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
929 curPos++;
930 switch (toupperW(*curPos)) {
931 case 'D': curPos++; expandDirs = TRUE; break;
932 case 'L': curPos++; useNumbers = TRUE; break;
934 /* Recursive is special case - /R can have an optional path following it */
935 /* filenamesets are another special case - /F can have an optional options following it */
936 case 'R':
937 case 'F':
939 BOOL isRecursive = (*curPos == 'R');
941 if (!isRecursive)
942 doFileset = TRUE;
944 /* Skip whitespace */
945 curPos++;
946 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
948 /* Next parm is either qualifier, path/options or variable -
949 only care about it if it is the path/options */
950 if (*curPos && *curPos != '/' && *curPos != '%') {
951 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
952 else {
953 static unsigned int once;
954 if (!once++) WINE_FIXME("/F needs to handle options\n");
957 break;
959 default:
960 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
961 curPos++;
964 /* Skip whitespace between qualifiers */
965 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
968 /* Skip whitespace before variable */
969 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
971 /* Ensure line continues with variable */
972 if (!*curPos || *curPos != '%') {
973 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
974 return;
977 /* Variable should follow */
978 i = 0;
979 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
980 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
981 variable[i] = 0x00;
982 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
983 curPos = &curPos[i];
985 /* Skip whitespace before IN */
986 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
988 /* Ensure line continues with IN */
989 if (!*curPos
990 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
992 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
993 return;
996 /* Save away where the set of data starts and the variable */
997 thisDepth = (*cmdList)->bracketDepth;
998 *cmdList = (*cmdList)->nextcommand;
999 setStart = (*cmdList);
1001 /* Skip until the close bracket */
1002 WINE_TRACE("Searching %p as the set\n", *cmdList);
1003 while (*cmdList &&
1004 (*cmdList)->command != NULL &&
1005 (*cmdList)->bracketDepth > thisDepth) {
1006 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1007 *cmdList = (*cmdList)->nextcommand;
1010 /* Skip the close bracket, if there is one */
1011 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1013 /* Syntax error if missing close bracket, or nothing following it
1014 and once we have the complete set, we expect a DO */
1015 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1016 if ((*cmdList == NULL)
1017 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1019 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1020 return;
1023 /* Save away the starting position for the commands (and offset for the
1024 first one */
1025 cmdStart = *cmdList;
1026 cmdEnd = *cmdList;
1027 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1028 itemNum = 0;
1030 thisSet = setStart;
1031 /* Loop through all set entries */
1032 while (thisSet &&
1033 thisSet->command != NULL &&
1034 thisSet->bracketDepth >= thisDepth) {
1036 /* Loop through all entries on the same line */
1037 WCHAR *item;
1038 WCHAR *itemStart;
1040 WINE_TRACE("Processing for set %p\n", thisSet);
1041 i = 0;
1042 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1045 * If the parameter within the set has a wildcard then search for matching files
1046 * otherwise do a literal substitution.
1048 static const WCHAR wildcards[] = {'*','?','\0'};
1049 thisCmdStart = cmdStart;
1051 itemNum++;
1052 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1054 if (!useNumbers && !doFileset) {
1055 if (strpbrkW (item, wildcards)) {
1056 hff = FindFirstFileW(item, &fd);
1057 if (hff != INVALID_HANDLE_VALUE) {
1058 do {
1059 BOOL isDirectory = FALSE;
1061 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1063 /* Handle as files or dirs appropriately, but ignore . and .. */
1064 if (isDirectory == expandDirs &&
1065 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1066 (strcmpW(fd.cFileName, dotW) != 0))
1068 thisCmdStart = cmdStart;
1069 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1070 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1071 fd.cFileName, FALSE, TRUE);
1074 } while (FindNextFileW(hff, &fd) != 0);
1075 FindClose (hff);
1077 } else {
1078 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1081 } else if (useNumbers) {
1082 /* Convert the first 3 numbers to signed longs and save */
1083 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1084 /* else ignore them! */
1086 /* Filesets - either a list of files, or a command to run and parse the output */
1087 } else if (doFileset && *itemStart != '"') {
1089 HANDLE input;
1090 WCHAR temp_file[MAX_PATH];
1092 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1093 wine_dbgstr_w(item));
1095 /* If backquote or single quote, we need to launch that command
1096 and parse the results - use a temporary file */
1097 if (*itemStart == '`' || *itemStart == '\'') {
1099 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1100 static const WCHAR redirOut[] = {'>','%','s','\0'};
1101 static const WCHAR cmdW[] = {'C','M','D','\0'};
1103 /* Remove trailing character */
1104 itemStart[strlenW(itemStart)-1] = 0x00;
1106 /* Get temp filename */
1107 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1108 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1110 /* Execute program and redirect output */
1111 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1112 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1114 /* Open the file, read line by line and process */
1115 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1116 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1117 } else {
1119 /* Open the file, read line by line and process */
1120 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1121 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1124 /* Process the input file */
1125 if (input == INVALID_HANDLE_VALUE) {
1126 WCMD_print_error ();
1127 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1128 errorlevel = 1;
1129 return; /* FOR loop aborts at first failure here */
1131 } else {
1133 WCHAR buffer[MAXSTRING] = {'\0'};
1134 WCHAR *where, *parm;
1136 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1138 /* Skip blank lines*/
1139 parm = WCMD_parameter (buffer, 0, &where, NULL);
1140 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1141 wine_dbgstr_w(buffer));
1143 if (where) {
1144 /* FIXME: The following should be moved into its own routine and
1145 reused for the string literal parsing below */
1146 thisCmdStart = cmdStart;
1147 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1148 cmdEnd = thisCmdStart;
1151 buffer[0] = 0x00;
1154 CloseHandle (input);
1157 /* Delete the temporary file */
1158 if (*itemStart == '`' || *itemStart == '\'') {
1159 DeleteFileW(temp_file);
1162 /* Filesets - A string literal */
1163 } else if (doFileset && *itemStart == '"') {
1164 WCHAR buffer[MAXSTRING] = {'\0'};
1165 WCHAR *where, *parm;
1167 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1168 strcpyW(buffer, item);
1169 parm = WCMD_parameter (buffer, 0, &where, NULL);
1170 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1171 wine_dbgstr_w(buffer));
1173 if (where) {
1174 /* FIXME: The following should be moved into its own routine and
1175 reused for the string literal parsing below */
1176 thisCmdStart = cmdStart;
1177 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1178 cmdEnd = thisCmdStart;
1182 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1183 cmdEnd = thisCmdStart;
1184 i++;
1187 /* Move onto the next set line */
1188 thisSet = thisSet->nextcommand;
1191 /* If /L is provided, now run the for loop */
1192 if (useNumbers) {
1193 WCHAR thisNum[20];
1194 static const WCHAR fmt[] = {'%','d','\0'};
1196 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1197 numbers[0], numbers[2], numbers[1]);
1198 for (i=numbers[0];
1199 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1200 i=i + numbers[1]) {
1202 sprintfW(thisNum, fmt, i);
1203 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1205 thisCmdStart = cmdStart;
1206 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1207 cmdEnd = thisCmdStart;
1211 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1212 all processing, OR it should be pointing to the end of && processing OR
1213 it should be pointing at the NULL end of bracket for the DO. The return
1214 value needs to be the NEXT command to execute, which it either is, or
1215 we need to step over the closing bracket */
1216 *cmdList = cmdEnd;
1217 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1221 /*****************************************************************************
1222 * WCMD_part_execute
1224 * Execute a command, and any && or bracketed follow on to the command. The
1225 * first command to be executed may not be at the front of the
1226 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1228 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1229 const WCHAR *variable, const WCHAR *value,
1230 BOOL isIF, BOOL conditionTRUE) {
1232 CMD_LIST *curPosition = *cmdList;
1233 int myDepth = (*cmdList)->bracketDepth;
1235 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1236 cmdList, wine_dbgstr_w(firstcmd),
1237 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1238 conditionTRUE);
1240 /* Skip leading whitespace between condition and the command */
1241 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1243 /* Process the first command, if there is one */
1244 if (conditionTRUE && firstcmd && *firstcmd) {
1245 WCHAR *command = WCMD_strdupW(firstcmd);
1246 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1247 HeapFree(GetProcessHeap(), 0, command);
1251 /* If it didn't move the position, step to next command */
1252 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1254 /* Process any other parts of the command */
1255 if (*cmdList) {
1256 BOOL processThese = TRUE;
1258 if (isIF) processThese = conditionTRUE;
1260 while (*cmdList) {
1261 static const WCHAR ifElse[] = {'e','l','s','e'};
1263 /* execute all appropriate commands */
1264 curPosition = *cmdList;
1266 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1267 *cmdList,
1268 (*cmdList)->prevDelim,
1269 (*cmdList)->bracketDepth, myDepth);
1271 /* Execute any statements appended to the line */
1272 /* FIXME: Only if previous call worked for && or failed for || */
1273 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1274 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1275 if (processThese && (*cmdList)->command) {
1276 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1277 value, cmdList);
1279 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1281 /* Execute any appended to the statement with (...) */
1282 } else if ((*cmdList)->bracketDepth > myDepth) {
1283 if (processThese) {
1284 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1285 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1287 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1289 /* End of the command - does 'ELSE ' follow as the next command? */
1290 } else {
1291 if (isIF
1292 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1293 (*cmdList)->command)) {
1295 /* Swap between if and else processing */
1296 processThese = !processThese;
1298 /* Process the ELSE part */
1299 if (processThese) {
1300 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1301 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1303 /* Skip leading whitespace between condition and the command */
1304 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1305 if (*cmd) {
1306 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1309 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1310 } else {
1311 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1312 break;
1317 return;
1320 /**************************************************************************
1321 * WCMD_give_help
1323 * Simple on-line help. Help text is stored in the resource file.
1326 void WCMD_give_help (const WCHAR *command) {
1328 int i;
1330 command = WCMD_skip_leading_spaces((WCHAR*) command);
1331 if (strlenW(command) == 0) {
1332 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1334 else {
1335 /* Display help message for builtin commands */
1336 for (i=0; i<=WCMD_EXIT; i++) {
1337 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1338 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1339 WCMD_output_asis (WCMD_LoadMessage(i));
1340 return;
1343 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1344 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1345 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1346 command, -1, externals[i], -1) == CSTR_EQUAL) {
1347 WCHAR cmd[128];
1348 static const WCHAR helpW[] = {' ', '/','?','\0'};
1349 strcpyW(cmd, command);
1350 strcatW(cmd, helpW);
1351 WCMD_run_program(cmd, 0);
1352 return;
1355 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1357 return;
1360 /****************************************************************************
1361 * WCMD_go_to
1363 * Batch file jump instruction. Not the most efficient algorithm ;-)
1364 * Prints error message if the specified label cannot be found - the file pointer is
1365 * then at EOF, effectively stopping the batch file.
1366 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1369 void WCMD_goto (CMD_LIST **cmdList) {
1371 WCHAR string[MAX_PATH];
1372 WCHAR current[MAX_PATH];
1374 /* Do not process any more parts of a processed multipart or multilines command */
1375 if (cmdList) *cmdList = NULL;
1377 if (context != NULL) {
1378 WCHAR *paramStart = param1, *str;
1379 static const WCHAR eofW[] = {':','e','o','f','\0'};
1381 if (param1[0] == 0x00) {
1382 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1383 return;
1386 /* Handle special :EOF label */
1387 if (lstrcmpiW (eofW, param1) == 0) {
1388 context -> skip_rest = TRUE;
1389 return;
1392 /* Support goto :label as well as goto label */
1393 if (*paramStart == ':') paramStart++;
1395 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1396 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1397 str = string;
1398 while (isspaceW (*str)) str++;
1399 if (*str == ':') {
1400 DWORD index = 0;
1401 str++;
1402 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1403 index++;
1405 /* ignore space at the end */
1406 current[index] = 0;
1407 if (lstrcmpiW (current, paramStart) == 0) return;
1410 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1412 return;
1415 /*****************************************************************************
1416 * WCMD_pushd
1418 * Push a directory onto the stack
1421 void WCMD_pushd (WCHAR *command) {
1422 struct env_stack *curdir;
1423 WCHAR *thisdir;
1424 static const WCHAR parmD[] = {'/','D','\0'};
1426 if (strchrW(command, '/') != NULL) {
1427 SetLastError(ERROR_INVALID_PARAMETER);
1428 WCMD_print_error();
1429 return;
1432 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1433 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1434 if( !curdir || !thisdir ) {
1435 LocalFree(curdir);
1436 LocalFree(thisdir);
1437 WINE_ERR ("out of memory\n");
1438 return;
1441 /* Change directory using CD code with /D parameter */
1442 strcpyW(quals, parmD);
1443 GetCurrentDirectoryW (1024, thisdir);
1444 errorlevel = 0;
1445 WCMD_setshow_default(command);
1446 if (errorlevel) {
1447 LocalFree(curdir);
1448 LocalFree(thisdir);
1449 return;
1450 } else {
1451 curdir -> next = pushd_directories;
1452 curdir -> strings = thisdir;
1453 if (pushd_directories == NULL) {
1454 curdir -> u.stackdepth = 1;
1455 } else {
1456 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1458 pushd_directories = curdir;
1463 /*****************************************************************************
1464 * WCMD_popd
1466 * Pop a directory from the stack
1469 void WCMD_popd (void) {
1470 struct env_stack *temp = pushd_directories;
1472 if (!pushd_directories)
1473 return;
1475 /* pop the old environment from the stack, and make it the current dir */
1476 pushd_directories = temp->next;
1477 SetCurrentDirectoryW(temp->strings);
1478 LocalFree (temp->strings);
1479 LocalFree (temp);
1482 /****************************************************************************
1483 * WCMD_if
1485 * Batch file conditional.
1487 * On entry, cmdlist will point to command containing the IF, and optionally
1488 * the first command to execute (if brackets not found)
1489 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1490 * If ('s were found, execute all within that bracket
1491 * Command may optionally be followed by an ELSE - need to skip instructions
1492 * in the else using the same logic
1494 * FIXME: Much more syntax checking needed!
1497 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1499 int negate; /* Negate condition */
1500 int test; /* Condition evaluation result */
1501 WCHAR condition[MAX_PATH], *command, *s;
1502 static const WCHAR notW[] = {'n','o','t','\0'};
1503 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1504 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1505 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1506 static const WCHAR eqeqW[] = {'=','=','\0'};
1507 static const WCHAR parmI[] = {'/','I','\0'};
1508 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1510 negate = !lstrcmpiW(param1,notW);
1511 strcpyW(condition, (negate ? param2 : param1));
1512 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1514 if (!lstrcmpiW (condition, errlvlW)) {
1515 test = (errorlevel >= atoiW(WCMD_parameter(p, 1+negate, NULL, NULL)));
1516 WCMD_parameter(p, 2+negate, &command, NULL);
1518 else if (!lstrcmpiW (condition, existW)) {
1519 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1520 WCMD_parameter(p, 2+negate, &command, NULL);
1522 else if (!lstrcmpiW (condition, defdW)) {
1523 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1524 WCMD_parameter(p, 2+negate, &command, NULL);
1526 else if ((s = strstrW (p, eqeqW))) {
1527 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1528 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1529 s += 2;
1530 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1531 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1532 test = caseInsensitive
1533 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1534 leftPart, leftPartEnd-leftPart+1,
1535 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1536 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1537 leftPart, leftPartEnd-leftPart+1,
1538 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1539 WCMD_parameter(s, 1, &command, NULL);
1541 else {
1542 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1543 return;
1546 /* Process rest of IF statement which is on the same line
1547 Note: This may process all or some of the cmdList (eg a GOTO) */
1548 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1551 /****************************************************************************
1552 * WCMD_move
1554 * Move a file, directory tree or wildcarded set of files.
1557 void WCMD_move (void) {
1559 int status;
1560 WIN32_FIND_DATAW fd;
1561 HANDLE hff;
1562 WCHAR input[MAX_PATH];
1563 WCHAR output[MAX_PATH];
1564 WCHAR drive[10];
1565 WCHAR dir[MAX_PATH];
1566 WCHAR fname[MAX_PATH];
1567 WCHAR ext[MAX_PATH];
1569 if (param1[0] == 0x00) {
1570 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1571 return;
1574 /* If no destination supplied, assume current directory */
1575 if (param2[0] == 0x00) {
1576 strcpyW(param2, dotW);
1579 /* If 2nd parm is directory, then use original filename */
1580 /* Convert partial path to full path */
1581 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1582 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1583 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1584 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1586 /* Split into components */
1587 WCMD_splitpath(input, drive, dir, fname, ext);
1589 hff = FindFirstFileW(input, &fd);
1590 if (hff == INVALID_HANDLE_VALUE)
1591 return;
1593 do {
1594 WCHAR dest[MAX_PATH];
1595 WCHAR src[MAX_PATH];
1596 DWORD attribs;
1598 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1600 /* Build src & dest name */
1601 strcpyW(src, drive);
1602 strcatW(src, dir);
1604 /* See if dest is an existing directory */
1605 attribs = GetFileAttributesW(output);
1606 if (attribs != INVALID_FILE_ATTRIBUTES &&
1607 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1608 strcpyW(dest, output);
1609 strcatW(dest, slashW);
1610 strcatW(dest, fd.cFileName);
1611 } else {
1612 strcpyW(dest, output);
1615 strcatW(src, fd.cFileName);
1617 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1618 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1620 /* Check if file is read only, otherwise move it */
1621 attribs = GetFileAttributesW(src);
1622 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1623 (attribs & FILE_ATTRIBUTE_READONLY)) {
1624 SetLastError(ERROR_ACCESS_DENIED);
1625 status = 0;
1626 } else {
1627 BOOL ok = TRUE;
1629 /* If destination exists, prompt unless /Y supplied */
1630 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1631 BOOL force = FALSE;
1632 WCHAR copycmd[MAXSTRING];
1633 int len;
1635 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1636 if (strstrW (quals, parmNoY))
1637 force = FALSE;
1638 else if (strstrW (quals, parmY))
1639 force = TRUE;
1640 else {
1641 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1642 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1643 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1644 && ! lstrcmpiW (copycmd, parmY));
1647 /* Prompt if overwriting */
1648 if (!force) {
1649 WCHAR question[MAXSTRING];
1650 WCHAR yesChar[10];
1652 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1654 /* Ask for confirmation */
1655 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1656 ok = WCMD_ask_confirm(question, FALSE, NULL);
1658 /* So delete the destination prior to the move */
1659 if (ok) {
1660 if (!DeleteFileW(dest)) {
1661 WCMD_print_error ();
1662 errorlevel = 1;
1663 ok = FALSE;
1669 if (ok) {
1670 status = MoveFileW(src, dest);
1671 } else {
1672 status = 1; /* Anything other than 0 to prevent error msg below */
1676 if (!status) {
1677 WCMD_print_error ();
1678 errorlevel = 1;
1680 } while (FindNextFileW(hff, &fd) != 0);
1682 FindClose(hff);
1685 /****************************************************************************
1686 * WCMD_pause
1688 * Suspend execution of a batch script until a key is typed
1691 void WCMD_pause (void)
1693 DWORD oldmode;
1694 BOOL have_console;
1695 DWORD count;
1696 WCHAR key;
1697 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1699 have_console = GetConsoleMode(hIn, &oldmode);
1700 if (have_console)
1701 SetConsoleMode(hIn, 0);
1703 WCMD_output(anykey);
1704 WCMD_ReadFile(hIn, &key, 1, &count);
1705 if (have_console)
1706 SetConsoleMode(hIn, oldmode);
1709 /****************************************************************************
1710 * WCMD_remove_dir
1712 * Delete a directory.
1715 void WCMD_remove_dir (WCHAR *command) {
1717 int argno = 0;
1718 int argsProcessed = 0;
1719 WCHAR *argN = command;
1720 static const WCHAR parmS[] = {'/','S','\0'};
1721 static const WCHAR parmQ[] = {'/','Q','\0'};
1723 /* Loop through all args */
1724 while (argN) {
1725 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1726 if (argN && argN[0] != '/') {
1727 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1728 wine_dbgstr_w(quals));
1729 argsProcessed++;
1731 /* If subdirectory search not supplied, just try to remove
1732 and report error if it fails (eg if it contains a file) */
1733 if (strstrW (quals, parmS) == NULL) {
1734 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1736 /* Otherwise use ShFileOp to recursively remove a directory */
1737 } else {
1739 SHFILEOPSTRUCTW lpDir;
1741 /* Ask first */
1742 if (strstrW (quals, parmQ) == NULL) {
1743 BOOL ok;
1744 WCHAR question[MAXSTRING];
1745 static const WCHAR fmt[] = {'%','s',' ','\0'};
1747 /* Ask for confirmation */
1748 wsprintfW(question, fmt, thisArg);
1749 ok = WCMD_ask_confirm(question, TRUE, NULL);
1751 /* Abort if answer is 'N' */
1752 if (!ok) return;
1755 /* Do the delete */
1756 lpDir.hwnd = NULL;
1757 lpDir.pTo = NULL;
1758 lpDir.pFrom = thisArg;
1759 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1760 lpDir.wFunc = FO_DELETE;
1761 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1766 /* Handle no valid args */
1767 if (argsProcessed == 0) {
1768 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1769 return;
1774 /****************************************************************************
1775 * WCMD_rename
1777 * Rename a file.
1780 void WCMD_rename (void) {
1782 int status;
1783 HANDLE hff;
1784 WIN32_FIND_DATAW fd;
1785 WCHAR input[MAX_PATH];
1786 WCHAR *dotDst = NULL;
1787 WCHAR drive[10];
1788 WCHAR dir[MAX_PATH];
1789 WCHAR fname[MAX_PATH];
1790 WCHAR ext[MAX_PATH];
1791 DWORD attribs;
1793 errorlevel = 0;
1795 /* Must be at least two args */
1796 if (param1[0] == 0x00 || param2[0] == 0x00) {
1797 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1798 errorlevel = 1;
1799 return;
1802 /* Destination cannot contain a drive letter or directory separator */
1803 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1804 SetLastError(ERROR_INVALID_PARAMETER);
1805 WCMD_print_error();
1806 errorlevel = 1;
1807 return;
1810 /* Convert partial path to full path */
1811 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1812 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1813 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1814 dotDst = strchrW(param2, '.');
1816 /* Split into components */
1817 WCMD_splitpath(input, drive, dir, fname, ext);
1819 hff = FindFirstFileW(input, &fd);
1820 if (hff == INVALID_HANDLE_VALUE)
1821 return;
1823 do {
1824 WCHAR dest[MAX_PATH];
1825 WCHAR src[MAX_PATH];
1826 WCHAR *dotSrc = NULL;
1827 int dirLen;
1829 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1831 /* FIXME: If dest name or extension is *, replace with filename/ext
1832 part otherwise use supplied name. This supports:
1833 ren *.fred *.jim
1834 ren jim.* fred.* etc
1835 However, windows has a more complex algorithm supporting eg
1836 ?'s and *'s mid name */
1837 dotSrc = strchrW(fd.cFileName, '.');
1839 /* Build src & dest name */
1840 strcpyW(src, drive);
1841 strcatW(src, dir);
1842 strcpyW(dest, src);
1843 dirLen = strlenW(src);
1844 strcatW(src, fd.cFileName);
1846 /* Build name */
1847 if (param2[0] == '*') {
1848 strcatW(dest, fd.cFileName);
1849 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1850 } else {
1851 strcatW(dest, param2);
1852 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1855 /* Build Extension */
1856 if (dotDst && (*(dotDst+1)=='*')) {
1857 if (dotSrc) strcatW(dest, dotSrc);
1858 } else if (dotDst) {
1859 if (dotDst) strcatW(dest, dotDst);
1862 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1863 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1865 /* Check if file is read only, otherwise move it */
1866 attribs = GetFileAttributesW(src);
1867 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1868 (attribs & FILE_ATTRIBUTE_READONLY)) {
1869 SetLastError(ERROR_ACCESS_DENIED);
1870 status = 0;
1871 } else {
1872 status = MoveFileW(src, dest);
1875 if (!status) {
1876 WCMD_print_error ();
1877 errorlevel = 1;
1879 } while (FindNextFileW(hff, &fd) != 0);
1881 FindClose(hff);
1884 /*****************************************************************************
1885 * WCMD_dupenv
1887 * Make a copy of the environment.
1889 static WCHAR *WCMD_dupenv( const WCHAR *env )
1891 WCHAR *env_copy;
1892 int len;
1894 if( !env )
1895 return NULL;
1897 len = 0;
1898 while ( env[len] )
1899 len += (strlenW(&env[len]) + 1);
1901 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1902 if (!env_copy)
1904 WINE_ERR("out of memory\n");
1905 return env_copy;
1907 memcpy (env_copy, env, len*sizeof (WCHAR));
1908 env_copy[len] = 0;
1910 return env_copy;
1913 /*****************************************************************************
1914 * WCMD_setlocal
1916 * setlocal pushes the environment onto a stack
1917 * Save the environment as unicode so we don't screw anything up.
1919 void WCMD_setlocal (const WCHAR *s) {
1920 WCHAR *env;
1921 struct env_stack *env_copy;
1922 WCHAR cwd[MAX_PATH];
1924 /* DISABLEEXTENSIONS ignored */
1926 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1927 if( !env_copy )
1929 WINE_ERR ("out of memory\n");
1930 return;
1933 env = GetEnvironmentStringsW ();
1935 env_copy->strings = WCMD_dupenv (env);
1936 if (env_copy->strings)
1938 env_copy->next = saved_environment;
1939 saved_environment = env_copy;
1941 /* Save the current drive letter */
1942 GetCurrentDirectoryW(MAX_PATH, cwd);
1943 env_copy->u.cwd = cwd[0];
1945 else
1946 LocalFree (env_copy);
1948 FreeEnvironmentStringsW (env);
1952 /*****************************************************************************
1953 * WCMD_endlocal
1955 * endlocal pops the environment off a stack
1956 * Note: When searching for '=', search from WCHAR position 1, to handle
1957 * special internal environment variables =C:, =D: etc
1959 void WCMD_endlocal (void) {
1960 WCHAR *env, *old, *p;
1961 struct env_stack *temp;
1962 int len, n;
1964 if (!saved_environment)
1965 return;
1967 /* pop the old environment from the stack */
1968 temp = saved_environment;
1969 saved_environment = temp->next;
1971 /* delete the current environment, totally */
1972 env = GetEnvironmentStringsW ();
1973 old = WCMD_dupenv (GetEnvironmentStringsW ());
1974 len = 0;
1975 while (old[len]) {
1976 n = strlenW(&old[len]) + 1;
1977 p = strchrW(&old[len] + 1, '=');
1978 if (p)
1980 *p++ = 0;
1981 SetEnvironmentVariableW (&old[len], NULL);
1983 len += n;
1985 LocalFree (old);
1986 FreeEnvironmentStringsW (env);
1988 /* restore old environment */
1989 env = temp->strings;
1990 len = 0;
1991 while (env[len]) {
1992 n = strlenW(&env[len]) + 1;
1993 p = strchrW(&env[len] + 1, '=');
1994 if (p)
1996 *p++ = 0;
1997 SetEnvironmentVariableW (&env[len], p);
1999 len += n;
2002 /* Restore current drive letter */
2003 if (IsCharAlphaW(temp->u.cwd)) {
2004 WCHAR envvar[4];
2005 WCHAR cwd[MAX_PATH];
2006 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2008 wsprintfW(envvar, fmt, temp->u.cwd);
2009 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2010 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2011 SetCurrentDirectoryW(cwd);
2015 LocalFree (env);
2016 LocalFree (temp);
2019 /*****************************************************************************
2020 * WCMD_setshow_default
2022 * Set/Show the current default directory
2025 void WCMD_setshow_default (const WCHAR *command) {
2027 BOOL status;
2028 WCHAR string[1024];
2029 WCHAR cwd[1024];
2030 WCHAR *pos;
2031 WIN32_FIND_DATAW fd;
2032 HANDLE hff;
2033 static const WCHAR parmD[] = {'/','D','\0'};
2035 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2037 /* Skip /D and trailing whitespace if on the front of the command line */
2038 if (CompareStringW(LOCALE_USER_DEFAULT,
2039 NORM_IGNORECASE | SORT_STRINGSORT,
2040 command, 2, parmD, -1) == CSTR_EQUAL) {
2041 command += 2;
2042 while (*command && (*command==' ' || *command=='\t'))
2043 command++;
2046 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2047 if (strlenW(command) == 0) {
2048 strcatW (cwd, newline);
2049 WCMD_output (cwd);
2051 else {
2052 /* Remove any double quotes, which may be in the
2053 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2054 pos = string;
2055 while (*command) {
2056 if (*command != '"') *pos++ = *command;
2057 command++;
2059 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2060 pos--;
2061 *pos = 0x00;
2063 /* Search for appropriate directory */
2064 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2065 hff = FindFirstFileW(string, &fd);
2066 if (hff != INVALID_HANDLE_VALUE) {
2067 do {
2068 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2069 WCHAR fpath[MAX_PATH];
2070 WCHAR drive[10];
2071 WCHAR dir[MAX_PATH];
2072 WCHAR fname[MAX_PATH];
2073 WCHAR ext[MAX_PATH];
2074 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2076 /* Convert path into actual directory spec */
2077 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2078 WCMD_splitpath(fpath, drive, dir, fname, ext);
2080 /* Rebuild path */
2081 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2082 break;
2084 } while (FindNextFileW(hff, &fd) != 0);
2085 FindClose(hff);
2088 /* Change to that directory */
2089 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2091 status = SetCurrentDirectoryW(string);
2092 if (!status) {
2093 errorlevel = 1;
2094 WCMD_print_error ();
2095 return;
2096 } else {
2098 /* Save away the actual new directory, to store as current location */
2099 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2101 /* Restore old directory if drive letter would change, and
2102 CD x:\directory /D (or pushd c:\directory) not supplied */
2103 if ((strstrW(quals, parmD) == NULL) &&
2104 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2105 SetCurrentDirectoryW(cwd);
2109 /* Set special =C: type environment variable, for drive letter of
2110 change of directory, even if path was restored due to missing
2111 /D (allows changing drive letter when not resident on that
2112 drive */
2113 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2114 WCHAR env[4];
2115 strcpyW(env, equalW);
2116 memcpy(env+1, string, 2 * sizeof(WCHAR));
2117 env[3] = 0x00;
2118 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2119 SetEnvironmentVariableW(env, string);
2123 return;
2126 /****************************************************************************
2127 * WCMD_setshow_date
2129 * Set/Show the system date
2130 * FIXME: Can't change date yet
2133 void WCMD_setshow_date (void) {
2135 WCHAR curdate[64], buffer[64];
2136 DWORD count;
2137 static const WCHAR parmT[] = {'/','T','\0'};
2139 if (strlenW(param1) == 0) {
2140 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2141 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2142 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2143 if (strstrW (quals, parmT) == NULL) {
2144 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2145 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2146 if (count > 2) {
2147 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2151 else WCMD_print_error ();
2153 else {
2154 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2158 /****************************************************************************
2159 * WCMD_compare
2161 static int WCMD_compare( const void *a, const void *b )
2163 int r;
2164 const WCHAR * const *str_a = a, * const *str_b = b;
2165 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2166 *str_a, -1, *str_b, -1 );
2167 if( r == CSTR_LESS_THAN ) return -1;
2168 if( r == CSTR_GREATER_THAN ) return 1;
2169 return 0;
2172 /****************************************************************************
2173 * WCMD_setshow_sortenv
2175 * sort variables into order for display
2176 * Optionally only display those who start with a stub
2177 * returns the count displayed
2179 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2181 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2182 const WCHAR **str;
2184 if (stub) stublen = strlenW(stub);
2186 /* count the number of strings, and the total length */
2187 while ( s[len] ) {
2188 len += (strlenW(&s[len]) + 1);
2189 count++;
2192 /* add the strings to an array */
2193 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2194 if( !str )
2195 return 0;
2196 str[0] = s;
2197 for( i=1; i<count; i++ )
2198 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2200 /* sort the array */
2201 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2203 /* print it */
2204 for( i=0; i<count; i++ ) {
2205 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2206 NORM_IGNORECASE | SORT_STRINGSORT,
2207 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2208 /* Don't display special internal variables */
2209 if (str[i][0] != '=') {
2210 WCMD_output_asis(str[i]);
2211 WCMD_output_asis(newline);
2212 displayedcount++;
2217 LocalFree( str );
2218 return displayedcount;
2221 /****************************************************************************
2222 * WCMD_setshow_env
2224 * Set/Show the environment variables
2227 void WCMD_setshow_env (WCHAR *s) {
2229 LPVOID env;
2230 WCHAR *p;
2231 int status;
2232 static const WCHAR parmP[] = {'/','P','\0'};
2234 if (param1[0] == 0x00 && quals[0] == 0x00) {
2235 env = GetEnvironmentStringsW();
2236 WCMD_setshow_sortenv( env, NULL );
2237 return;
2240 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2241 if (CompareStringW(LOCALE_USER_DEFAULT,
2242 NORM_IGNORECASE | SORT_STRINGSORT,
2243 s, 2, parmP, -1) == CSTR_EQUAL) {
2244 WCHAR string[MAXSTRING];
2245 DWORD count;
2247 s += 2;
2248 while (*s && (*s==' ' || *s=='\t')) s++;
2249 if (*s=='\"')
2250 WCMD_opt_s_strip_quotes(s);
2252 /* If no parameter, or no '=' sign, return an error */
2253 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2254 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2255 return;
2258 /* Output the prompt */
2259 *p++ = '\0';
2260 if (strlenW(p) != 0) WCMD_output(p);
2262 /* Read the reply */
2263 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2264 if (count > 1) {
2265 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2266 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2267 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2268 wine_dbgstr_w(string));
2269 status = SetEnvironmentVariableW(s, string);
2272 } else {
2273 DWORD gle;
2275 if (*s=='\"')
2276 WCMD_opt_s_strip_quotes(s);
2277 p = strchrW (s, '=');
2278 if (p == NULL) {
2279 env = GetEnvironmentStringsW();
2280 if (WCMD_setshow_sortenv( env, s ) == 0) {
2281 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2282 errorlevel = 1;
2284 return;
2286 *p++ = '\0';
2288 if (strlenW(p) == 0) p = NULL;
2289 status = SetEnvironmentVariableW(s, p);
2290 gle = GetLastError();
2291 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2292 errorlevel = 1;
2293 } else if ((!status)) WCMD_print_error();
2297 /****************************************************************************
2298 * WCMD_setshow_path
2300 * Set/Show the path environment variable
2303 void WCMD_setshow_path (const WCHAR *command) {
2305 WCHAR string[1024];
2306 DWORD status;
2307 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2308 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2310 if (strlenW(param1) == 0) {
2311 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2312 if (status != 0) {
2313 WCMD_output_asis ( pathEqW);
2314 WCMD_output_asis ( string);
2315 WCMD_output_asis ( newline);
2317 else {
2318 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2321 else {
2322 if (*command == '=') command++; /* Skip leading '=' */
2323 status = SetEnvironmentVariableW(pathW, command);
2324 if (!status) WCMD_print_error();
2328 /****************************************************************************
2329 * WCMD_setshow_prompt
2331 * Set or show the command prompt.
2334 void WCMD_setshow_prompt (void) {
2336 WCHAR *s;
2337 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2339 if (strlenW(param1) == 0) {
2340 SetEnvironmentVariableW(promptW, NULL);
2342 else {
2343 s = param1;
2344 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2345 if (strlenW(s) == 0) {
2346 SetEnvironmentVariableW(promptW, NULL);
2348 else SetEnvironmentVariableW(promptW, s);
2352 /****************************************************************************
2353 * WCMD_setshow_time
2355 * Set/Show the system time
2356 * FIXME: Can't change time yet
2359 void WCMD_setshow_time (void) {
2361 WCHAR curtime[64], buffer[64];
2362 DWORD count;
2363 SYSTEMTIME st;
2364 static const WCHAR parmT[] = {'/','T','\0'};
2366 if (strlenW(param1) == 0) {
2367 GetLocalTime(&st);
2368 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2369 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2370 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2371 if (strstrW (quals, parmT) == NULL) {
2372 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2373 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2374 if (count > 2) {
2375 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2379 else WCMD_print_error ();
2381 else {
2382 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2386 /****************************************************************************
2387 * WCMD_shift
2389 * Shift batch parameters.
2390 * Optional /n says where to start shifting (n=0-8)
2393 void WCMD_shift (const WCHAR *command) {
2394 int start;
2396 if (context != NULL) {
2397 WCHAR *pos = strchrW(command, '/');
2398 int i;
2400 if (pos == NULL) {
2401 start = 0;
2402 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2403 start = (*(pos+1) - '0');
2404 } else {
2405 SetLastError(ERROR_INVALID_PARAMETER);
2406 WCMD_print_error();
2407 return;
2410 WINE_TRACE("Shifting variables, starting at %d\n", start);
2411 for (i=start;i<=8;i++) {
2412 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2414 context -> shift_count[9] = context -> shift_count[9] + 1;
2419 /****************************************************************************
2420 * WCMD_title
2422 * Set the console title
2424 void WCMD_title (const WCHAR *command) {
2425 SetConsoleTitleW(command);
2428 /****************************************************************************
2429 * WCMD_type
2431 * Copy a file to standard output.
2434 void WCMD_type (WCHAR *command) {
2436 int argno = 0;
2437 WCHAR *argN = command;
2438 BOOL writeHeaders = FALSE;
2440 if (param1[0] == 0x00) {
2441 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2442 return;
2445 if (param2[0] != 0x00) writeHeaders = TRUE;
2447 /* Loop through all args */
2448 errorlevel = 0;
2449 while (argN) {
2450 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2452 HANDLE h;
2453 WCHAR buffer[512];
2454 DWORD count;
2456 if (!argN) break;
2458 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2459 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2460 FILE_ATTRIBUTE_NORMAL, NULL);
2461 if (h == INVALID_HANDLE_VALUE) {
2462 WCMD_print_error ();
2463 WCMD_output(WCMD_LoadMessage(WCMD_READFAIL), thisArg); /* should be _stderr */
2464 errorlevel = 1;
2465 } else {
2466 if (writeHeaders) {
2467 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2468 WCMD_output(fmt, thisArg);
2470 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2471 if (count == 0) break; /* ReadFile reports success on EOF! */
2472 buffer[count] = 0;
2473 WCMD_output_asis (buffer);
2475 CloseHandle (h);
2480 /****************************************************************************
2481 * WCMD_more
2483 * Output either a file or stdin to screen in pages
2486 void WCMD_more (WCHAR *command) {
2488 int argno = 0;
2489 WCHAR *argN = command;
2490 WCHAR moreStr[100];
2491 WCHAR moreStrPage[100];
2492 WCHAR buffer[512];
2493 DWORD count;
2494 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2495 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2496 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2497 ')',' ','-','-','\n','\0'};
2498 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2500 /* Prefix the NLS more with '-- ', then load the text */
2501 errorlevel = 0;
2502 strcpyW(moreStr, moreStart);
2503 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2504 (sizeof(moreStr)/sizeof(WCHAR))-3);
2506 if (param1[0] == 0x00) {
2508 /* Wine implements pipes via temporary files, and hence stdin is
2509 effectively reading from the file. This means the prompts for
2510 more are satisfied by the next line from the input (file). To
2511 avoid this, ensure stdin is to the console */
2512 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2513 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2514 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2515 FILE_ATTRIBUTE_NORMAL, 0);
2516 WINE_TRACE("No parms - working probably in pipe mode\n");
2517 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2519 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2520 once you get in this bit unless due to a pipe, its going to end badly... */
2521 wsprintfW(moreStrPage, moreFmt, moreStr);
2523 WCMD_enter_paged_mode(moreStrPage);
2524 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2525 if (count == 0) break; /* ReadFile reports success on EOF! */
2526 buffer[count] = 0;
2527 WCMD_output_asis (buffer);
2529 WCMD_leave_paged_mode();
2531 /* Restore stdin to what it was */
2532 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2533 CloseHandle(hConIn);
2535 return;
2536 } else {
2537 BOOL needsPause = FALSE;
2539 /* Loop through all args */
2540 WINE_TRACE("Parms supplied - working through each file\n");
2541 WCMD_enter_paged_mode(moreStrPage);
2543 while (argN) {
2544 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2545 HANDLE h;
2547 if (!argN) break;
2549 if (needsPause) {
2551 /* Wait */
2552 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2553 WCMD_leave_paged_mode();
2554 WCMD_output_asis(moreStrPage);
2555 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2556 WCMD_enter_paged_mode(moreStrPage);
2560 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2561 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2562 FILE_ATTRIBUTE_NORMAL, NULL);
2563 if (h == INVALID_HANDLE_VALUE) {
2564 WCMD_print_error ();
2565 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2566 errorlevel = 1;
2567 } else {
2568 ULONG64 curPos = 0;
2569 ULONG64 fileLen = 0;
2570 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2572 /* Get the file size */
2573 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2574 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2576 needsPause = TRUE;
2577 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2578 if (count == 0) break; /* ReadFile reports success on EOF! */
2579 buffer[count] = 0;
2580 curPos += count;
2582 /* Update % count (would be used in WCMD_output_asis as prompt) */
2583 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2585 WCMD_output_asis (buffer);
2587 CloseHandle (h);
2591 WCMD_leave_paged_mode();
2595 /****************************************************************************
2596 * WCMD_verify
2598 * Display verify flag.
2599 * FIXME: We don't actually do anything with the verify flag other than toggle
2600 * it...
2603 void WCMD_verify (const WCHAR *command) {
2605 int count;
2607 count = strlenW(command);
2608 if (count == 0) {
2609 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2610 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2611 return;
2613 if (lstrcmpiW(command, onW) == 0) {
2614 verify_mode = TRUE;
2615 return;
2617 else if (lstrcmpiW(command, offW) == 0) {
2618 verify_mode = FALSE;
2619 return;
2621 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2624 /****************************************************************************
2625 * WCMD_version
2627 * Display version info.
2630 void WCMD_version (void) {
2632 WCMD_output (version_string);
2636 /****************************************************************************
2637 * WCMD_volume
2639 * Display volume information (set_label = FALSE)
2640 * Additionally set volume label (set_label = TRUE)
2641 * Returns 1 on success, 0 otherwise
2644 int WCMD_volume(BOOL set_label, const WCHAR *path)
2646 DWORD count, serial;
2647 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2648 BOOL status;
2650 if (strlenW(path) == 0) {
2651 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2652 if (!status) {
2653 WCMD_print_error ();
2654 return 0;
2656 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2657 &serial, NULL, NULL, NULL, 0);
2659 else {
2660 static const WCHAR fmt[] = {'%','s','\\','\0'};
2661 if ((path[1] != ':') || (strlenW(path) != 2)) {
2662 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2663 return 0;
2665 wsprintfW (curdir, fmt, path);
2666 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2667 &serial, NULL,
2668 NULL, NULL, 0);
2670 if (!status) {
2671 WCMD_print_error ();
2672 return 0;
2674 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2675 curdir[0], label, HIWORD(serial), LOWORD(serial));
2676 if (set_label) {
2677 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2678 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2679 if (count > 1) {
2680 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2681 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2683 if (strlenW(path) != 0) {
2684 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2686 else {
2687 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2690 return 1;
2693 /**************************************************************************
2694 * WCMD_exit
2696 * Exit either the process, or just this batch program
2700 void WCMD_exit (CMD_LIST **cmdList) {
2702 static const WCHAR parmB[] = {'/','B','\0'};
2703 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2705 if (context && lstrcmpiW(quals, parmB) == 0) {
2706 errorlevel = rc;
2707 context -> skip_rest = TRUE;
2708 *cmdList = NULL;
2709 } else {
2710 ExitProcess(rc);
2715 /*****************************************************************************
2716 * WCMD_assoc
2718 * Lists or sets file associations (assoc = TRUE)
2719 * Lists or sets file types (assoc = FALSE)
2721 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2723 HKEY key;
2724 DWORD accessOptions = KEY_READ;
2725 WCHAR *newValue;
2726 LONG rc = ERROR_SUCCESS;
2727 WCHAR keyValue[MAXSTRING];
2728 DWORD valueLen = MAXSTRING;
2729 HKEY readKey;
2730 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2731 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2733 /* See if parameter includes '=' */
2734 errorlevel = 0;
2735 newValue = strchrW(command, '=');
2736 if (newValue) accessOptions |= KEY_WRITE;
2738 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2739 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2740 accessOptions, &key) != ERROR_SUCCESS) {
2741 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2742 return;
2745 /* If no parameters then list all associations */
2746 if (*command == 0x00) {
2747 int index = 0;
2749 /* Enumerate all the keys */
2750 while (rc != ERROR_NO_MORE_ITEMS) {
2751 WCHAR keyName[MAXSTRING];
2752 DWORD nameLen;
2754 /* Find the next value */
2755 nameLen = MAXSTRING;
2756 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2758 if (rc == ERROR_SUCCESS) {
2760 /* Only interested in extension ones if assoc, or others
2761 if not assoc */
2762 if ((keyName[0] == '.' && assoc) ||
2763 (!(keyName[0] == '.') && (!assoc)))
2765 WCHAR subkey[MAXSTRING];
2766 strcpyW(subkey, keyName);
2767 if (!assoc) strcatW(subkey, shOpCmdW);
2769 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2771 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2772 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2773 WCMD_output_asis(keyName);
2774 WCMD_output_asis(equalW);
2775 /* If no default value found, leave line empty after '=' */
2776 if (rc == ERROR_SUCCESS) {
2777 WCMD_output_asis(keyValue);
2779 WCMD_output_asis(newline);
2780 RegCloseKey(readKey);
2786 } else {
2788 /* Parameter supplied - if no '=' on command line, its a query */
2789 if (newValue == NULL) {
2790 WCHAR *space;
2791 WCHAR subkey[MAXSTRING];
2793 /* Query terminates the parameter at the first space */
2794 strcpyW(keyValue, command);
2795 space = strchrW(keyValue, ' ');
2796 if (space) *space=0x00;
2798 /* Set up key name */
2799 strcpyW(subkey, keyValue);
2800 if (!assoc) strcatW(subkey, shOpCmdW);
2802 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2804 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2805 WCMD_output_asis(command);
2806 WCMD_output_asis(equalW);
2807 /* If no default value found, leave line empty after '=' */
2808 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2809 WCMD_output_asis(newline);
2810 RegCloseKey(readKey);
2812 } else {
2813 WCHAR msgbuffer[MAXSTRING];
2814 WCHAR outbuffer[MAXSTRING];
2816 /* Load the translated 'File association not found' */
2817 if (assoc) {
2818 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2819 } else {
2820 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2822 wsprintfW(outbuffer, msgbuffer, keyValue);
2823 WCMD_output_asis_stderr(outbuffer);
2824 errorlevel = 2;
2827 /* Not a query - its a set or clear of a value */
2828 } else {
2830 WCHAR subkey[MAXSTRING];
2832 /* Get pointer to new value */
2833 *newValue = 0x00;
2834 newValue++;
2836 /* Set up key name */
2837 strcpyW(subkey, command);
2838 if (!assoc) strcatW(subkey, shOpCmdW);
2840 /* If nothing after '=' then clear value - only valid for ASSOC */
2841 if (*newValue == 0x00) {
2843 if (assoc) rc = RegDeleteKeyW(key, command);
2844 if (assoc && rc == ERROR_SUCCESS) {
2845 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2847 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2848 WCMD_print_error();
2849 errorlevel = 2;
2851 } else {
2852 WCHAR msgbuffer[MAXSTRING];
2853 WCHAR outbuffer[MAXSTRING];
2855 /* Load the translated 'File association not found' */
2856 if (assoc) {
2857 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2858 sizeof(msgbuffer)/sizeof(WCHAR));
2859 } else {
2860 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2861 sizeof(msgbuffer)/sizeof(WCHAR));
2863 wsprintfW(outbuffer, msgbuffer, keyValue);
2864 WCMD_output_asis_stderr(outbuffer);
2865 errorlevel = 2;
2868 /* It really is a set value = contents */
2869 } else {
2870 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2871 accessOptions, NULL, &readKey, NULL);
2872 if (rc == ERROR_SUCCESS) {
2873 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2874 (LPBYTE)newValue,
2875 sizeof(WCHAR) * (strlenW(newValue) + 1));
2876 RegCloseKey(readKey);
2879 if (rc != ERROR_SUCCESS) {
2880 WCMD_print_error();
2881 errorlevel = 2;
2882 } else {
2883 WCMD_output_asis(command);
2884 WCMD_output_asis(equalW);
2885 WCMD_output_asis(newValue);
2886 WCMD_output_asis(newline);
2892 /* Clean up */
2893 RegCloseKey(key);
2896 /****************************************************************************
2897 * WCMD_color
2899 * Colors the terminal screen.
2902 void WCMD_color (void) {
2904 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2905 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2907 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2908 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
2909 return;
2912 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2914 COORD topLeft;
2915 DWORD screenSize;
2916 DWORD color = 0;
2918 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2920 topLeft.X = 0;
2921 topLeft.Y = 0;
2923 /* Convert the color hex digits */
2924 if (param1[0] == 0x00) {
2925 color = defaultColor;
2926 } else {
2927 color = strtoulW(param1, NULL, 16);
2930 /* Fail if fg == bg color */
2931 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2932 errorlevel = 1;
2933 return;
2936 /* Set the current screen contents and ensure all future writes
2937 remain this color */
2938 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2939 SetConsoleTextAttribute(hStdOut, color);