cmd: Fix comments in WCMD_color.
[wine/multimedia.git] / programs / cmd / builtins.c
blob9fe50d9116dcb3ac1a79dc4124a6cdee96dce990
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,
119 sizeof(answer)/sizeof(WCHAR), &count, NULL);
120 answer[0] = toupperW(answer[0]);
123 /* Return the answer */
124 return ((answer[0] == Ybuffer[0]) ||
125 (optionAll && (answer[0] == Abuffer[0])));
128 /****************************************************************************
129 * WCMD_clear_screen
131 * Clear the terminal screen.
134 void WCMD_clear_screen (void) {
136 /* Emulate by filling the screen from the top left to bottom right with
137 spaces, then moving the cursor to the top left afterwards */
138 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
139 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
141 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
143 COORD topLeft;
144 DWORD screenSize;
146 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
148 topLeft.X = 0;
149 topLeft.Y = 0;
150 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
151 SetConsoleCursorPosition(hStdOut, topLeft);
155 /****************************************************************************
156 * WCMD_change_tty
158 * Change the default i/o device (ie redirect STDin/STDout).
161 void WCMD_change_tty (void) {
163 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
167 /****************************************************************************
168 * WCMD_choice
172 void WCMD_choice (const WCHAR * command) {
174 static const WCHAR bellW[] = {7,0};
175 static const WCHAR commaW[] = {',',0};
176 static const WCHAR bracket_open[] = {'[',0};
177 static const WCHAR bracket_close[] = {']','?',0};
178 WCHAR answer[16];
179 WCHAR buffer[16];
180 WCHAR *ptr = NULL;
181 WCHAR *opt_c = NULL;
182 WCHAR *my_command = NULL;
183 WCHAR opt_default = 0;
184 DWORD opt_timeout = 0;
185 DWORD count;
186 DWORD oldmode;
187 DWORD have_console;
188 BOOL opt_n = FALSE;
189 BOOL opt_s = FALSE;
191 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
192 errorlevel = 0;
194 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
195 if (!my_command)
196 return;
198 ptr = WCMD_skip_leading_spaces(my_command);
199 while (*ptr == '/') {
200 switch (toupperW(ptr[1])) {
201 case 'C':
202 ptr += 2;
203 /* the colon is optional */
204 if (*ptr == ':')
205 ptr++;
207 if (!*ptr || isspaceW(*ptr)) {
208 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
209 HeapFree(GetProcessHeap(), 0, my_command);
210 return;
213 /* remember the allowed keys (overwrite previous /C option) */
214 opt_c = ptr;
215 while (*ptr && (!isspaceW(*ptr)))
216 ptr++;
218 if (*ptr) {
219 /* terminate allowed chars */
220 *ptr = 0;
221 ptr = WCMD_skip_leading_spaces(&ptr[1]);
223 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
224 break;
226 case 'N':
227 opt_n = TRUE;
228 ptr = WCMD_skip_leading_spaces(&ptr[2]);
229 break;
231 case 'S':
232 opt_s = TRUE;
233 ptr = WCMD_skip_leading_spaces(&ptr[2]);
234 break;
236 case 'T':
237 ptr = &ptr[2];
238 /* the colon is optional */
239 if (*ptr == ':')
240 ptr++;
242 opt_default = *ptr++;
244 if (!opt_default || (*ptr != ',')) {
245 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
246 HeapFree(GetProcessHeap(), 0, my_command);
247 return;
249 ptr++;
251 count = 0;
252 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
253 count++;
254 ptr++;
257 answer[count] = 0;
258 opt_timeout = atoiW(answer);
260 ptr = WCMD_skip_leading_spaces(ptr);
261 break;
263 default:
264 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
265 HeapFree(GetProcessHeap(), 0, my_command);
266 return;
270 if (opt_timeout)
271 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
273 if (have_console)
274 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
276 /* use default keys, when needed: localized versions of "Y"es and "No" */
277 if (!opt_c) {
278 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
279 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
280 opt_c = buffer;
281 buffer[2] = 0;
284 /* print the question, when needed */
285 if (*ptr)
286 WCMD_output_asis(ptr);
288 if (!opt_s) {
289 struprW(opt_c);
290 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
293 if (!opt_n) {
294 /* print a list of all allowed answers inside brackets */
295 WCMD_output_asis(bracket_open);
296 ptr = opt_c;
297 answer[1] = 0;
298 while ((answer[0] = *ptr++)) {
299 WCMD_output_asis(answer);
300 if (*ptr)
301 WCMD_output_asis(commaW);
303 WCMD_output_asis(bracket_close);
306 while (TRUE) {
308 /* FIXME: Add support for option /T */
309 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count, NULL);
311 if (!opt_s)
312 answer[0] = toupperW(answer[0]);
314 ptr = strchrW(opt_c, answer[0]);
315 if (ptr) {
316 WCMD_output_asis(answer);
317 WCMD_output(newline);
318 if (have_console)
319 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
321 errorlevel = (ptr - opt_c) + 1;
322 WINE_TRACE("answer: %d\n", errorlevel);
323 HeapFree(GetProcessHeap(), 0, my_command);
324 return;
326 else
328 /* key not allowed: play the bell */
329 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
330 WCMD_output_asis(bellW);
335 /****************************************************************************
336 * WCMD_copy
338 * Copy a file or wildcarded set.
339 * FIXME: Add support for a+b+c type syntax
342 void WCMD_copy (void) {
344 WIN32_FIND_DATAW fd;
345 HANDLE hff;
346 BOOL force, status;
347 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
348 DWORD len;
349 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
350 BOOL copyToDir = FALSE;
351 WCHAR srcspec[MAX_PATH];
352 DWORD attribs;
353 WCHAR drive[10];
354 WCHAR dir[MAX_PATH];
355 WCHAR fname[MAX_PATH];
356 WCHAR ext[MAX_PATH];
358 if (param1[0] == 0x00) {
359 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
360 return;
363 /* Convert source into full spec */
364 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
365 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
366 if (srcpath[strlenW(srcpath) - 1] == '\\')
367 srcpath[strlenW(srcpath) - 1] = '\0';
369 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
370 attribs = GetFileAttributesW(srcpath);
371 } else {
372 attribs = 0;
374 strcpyW(srcspec, srcpath);
376 /* If a directory, then add \* on the end when searching */
377 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
378 strcatW(srcpath, slashW);
379 strcatW(srcspec, slashW);
380 strcatW(srcspec, starW);
381 } else {
382 WCMD_splitpath(srcpath, drive, dir, fname, ext);
383 strcpyW(srcpath, drive);
384 strcatW(srcpath, dir);
387 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
389 /* If no destination supplied, assume current directory */
390 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
391 if (param2[0] == 0x00) {
392 strcpyW(param2, dotW);
395 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
396 if (outpath[strlenW(outpath) - 1] == '\\')
397 outpath[strlenW(outpath) - 1] = '\0';
398 attribs = GetFileAttributesW(outpath);
399 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
400 strcatW (outpath, slashW);
401 copyToDir = TRUE;
403 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
404 wine_dbgstr_w(outpath), copyToDir);
406 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
407 if (strstrW (quals, parmNoY))
408 force = FALSE;
409 else if (strstrW (quals, parmY))
410 force = TRUE;
411 else {
412 /* By default, we will force the overwrite in batch mode and ask for
413 * confirmation in interactive mode. */
414 force = !!context;
416 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
417 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
418 * default behavior. */
419 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
420 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
421 if (!lstrcmpiW (copycmd, parmY))
422 force = TRUE;
423 else if (!lstrcmpiW (copycmd, parmNoY))
424 force = FALSE;
428 /* Loop through all source files */
429 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
430 hff = FindFirstFileW(srcspec, &fd);
431 if (hff != INVALID_HANDLE_VALUE) {
432 do {
433 WCHAR outname[MAX_PATH];
434 WCHAR srcname[MAX_PATH];
435 BOOL overwrite = force;
437 /* Destination is either supplied filename, or source name in
438 supplied destination directory */
439 strcpyW(outname, outpath);
440 if (copyToDir) strcatW(outname, fd.cFileName);
441 strcpyW(srcname, srcpath);
442 strcatW(srcname, fd.cFileName);
444 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
445 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
447 /* Skip . and .., and directories */
448 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
449 overwrite = FALSE;
450 WINE_TRACE("Skipping directories\n");
453 /* Prompt before overwriting */
454 else if (!overwrite) {
455 attribs = GetFileAttributesW(outname);
456 if (attribs != INVALID_FILE_ATTRIBUTES) {
457 WCHAR buffer[MAXSTRING];
458 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
459 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
461 else overwrite = TRUE;
464 /* Do the copy as appropriate */
465 if (overwrite) {
466 status = CopyFileW(srcname, outname, FALSE);
467 if (!status) WCMD_print_error ();
470 } while (FindNextFileW(hff, &fd) != 0);
471 FindClose (hff);
472 } else {
473 status = ERROR_FILE_NOT_FOUND;
474 WCMD_print_error ();
478 /****************************************************************************
479 * WCMD_create_dir
481 * Create a directory (and, if needed, any intermediate directories).
483 * Modifies its argument by replacing slashes temporarily with nulls.
486 static BOOL create_full_path(WCHAR* path)
488 WCHAR *p, *start;
490 /* don't mess with drive letter portion of path, if any */
491 start = path;
492 if (path[1] == ':')
493 start = path+2;
495 /* Strip trailing slashes. */
496 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
497 *p = 0;
499 /* Step through path, creating intermediate directories as needed. */
500 /* First component includes drive letter, if any. */
501 p = start;
502 for (;;) {
503 DWORD rv;
504 /* Skip to end of component */
505 while (*p == '\\') p++;
506 while (*p && *p != '\\') p++;
507 if (!*p) {
508 /* path is now the original full path */
509 return CreateDirectoryW(path, NULL);
511 /* Truncate path, create intermediate directory, and restore path */
512 *p = 0;
513 rv = CreateDirectoryW(path, NULL);
514 *p = '\\';
515 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
516 return FALSE;
518 /* notreached */
519 return FALSE;
522 void WCMD_create_dir (WCHAR *command) {
523 int argno = 0;
524 WCHAR *argN = command;
526 if (param1[0] == 0x00) {
527 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
528 return;
530 /* Loop through all args */
531 while (TRUE) {
532 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL);
533 if (!argN) break;
534 if (!create_full_path(thisArg)) {
535 WCMD_print_error ();
536 errorlevel = 1;
541 /* Parse the /A options given by the user on the commandline
542 * into a bitmask of wanted attributes (*wantSet),
543 * and a bitmask of unwanted attributes (*wantClear).
545 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
546 static const WCHAR parmA[] = {'/','A','\0'};
547 WCHAR *p;
549 /* both are strictly 'out' parameters */
550 *wantSet=0;
551 *wantClear=0;
553 /* For each /A argument */
554 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
555 /* Skip /A itself */
556 p += 2;
558 /* Skip optional : */
559 if (*p == ':') p++;
561 /* For each of the attribute specifier chars to this /A option */
562 for (; *p != 0 && *p != '/'; p++) {
563 BOOL negate = FALSE;
564 DWORD mask = 0;
566 if (*p == '-') {
567 negate=TRUE;
568 p++;
571 /* Convert the attribute specifier to a bit in one of the masks */
572 switch (*p) {
573 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
574 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
575 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
576 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
577 default:
578 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
580 if (negate)
581 *wantClear |= mask;
582 else
583 *wantSet |= mask;
588 /* If filename part of parameter is * or *.*,
589 * and neither /Q nor /P options were given,
590 * prompt the user whether to proceed.
591 * Returns FALSE if user says no, TRUE otherwise.
592 * *pPrompted is set to TRUE if the user is prompted.
593 * (If /P supplied, del will prompt for individual files later.)
595 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
596 static const WCHAR parmP[] = {'/','P','\0'};
597 static const WCHAR parmQ[] = {'/','Q','\0'};
599 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
600 static const WCHAR anyExt[]= {'.','*','\0'};
601 WCHAR drive[10];
602 WCHAR dir[MAX_PATH];
603 WCHAR fname[MAX_PATH];
604 WCHAR ext[MAX_PATH];
605 WCHAR fpath[MAX_PATH];
607 /* Convert path into actual directory spec */
608 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
609 WCMD_splitpath(fpath, drive, dir, fname, ext);
611 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
612 if ((strcmpW(fname, starW) == 0) &&
613 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
615 WCHAR question[MAXSTRING];
616 static const WCHAR fmt[] = {'%','s',' ','\0'};
618 /* Caller uses this to suppress "file not found" warning later */
619 *pPrompted = TRUE;
621 /* Ask for confirmation */
622 wsprintfW(question, fmt, fpath);
623 return WCMD_ask_confirm(question, TRUE, NULL);
626 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
627 return TRUE;
630 /* Helper function for WCMD_delete().
631 * Deletes a single file, directory, or wildcard.
632 * If /S was given, does it recursively.
633 * Returns TRUE if a file was deleted.
635 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
637 static const WCHAR parmP[] = {'/','P','\0'};
638 static const WCHAR parmS[] = {'/','S','\0'};
639 static const WCHAR parmF[] = {'/','F','\0'};
640 DWORD wanted_attrs;
641 DWORD unwanted_attrs;
642 BOOL found = FALSE;
643 WCHAR argCopy[MAX_PATH];
644 WIN32_FIND_DATAW fd;
645 HANDLE hff;
646 WCHAR fpath[MAX_PATH];
647 WCHAR *p;
648 BOOL handleParm = TRUE;
650 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
652 strcpyW(argCopy, thisArg);
653 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
654 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
656 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
657 /* Skip this arg if user declines to delete *.* */
658 return FALSE;
661 /* First, try to delete in the current directory */
662 hff = FindFirstFileW(argCopy, &fd);
663 if (hff == INVALID_HANDLE_VALUE) {
664 handleParm = FALSE;
665 } else {
666 found = TRUE;
669 /* Support del <dirname> by just deleting all files dirname\* */
670 if (handleParm
671 && (strchrW(argCopy,'*') == NULL)
672 && (strchrW(argCopy,'?') == NULL)
673 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
675 WCHAR modifiedParm[MAX_PATH];
676 static const WCHAR slashStar[] = {'\\','*','\0'};
678 strcpyW(modifiedParm, argCopy);
679 strcatW(modifiedParm, slashStar);
680 FindClose(hff);
681 found = TRUE;
682 WCMD_delete_one(modifiedParm);
684 } else if (handleParm) {
686 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
687 strcpyW (fpath, argCopy);
688 do {
689 p = strrchrW (fpath, '\\');
690 if (p != NULL) {
691 *++p = '\0';
692 strcatW (fpath, fd.cFileName);
694 else strcpyW (fpath, fd.cFileName);
695 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
696 BOOL ok;
698 /* Handle attribute matching (/A) */
699 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
700 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
702 /* /P means prompt for each file */
703 if (ok && strstrW (quals, parmP) != NULL) {
704 WCHAR question[MAXSTRING];
706 /* Ask for confirmation */
707 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
708 ok = WCMD_ask_confirm(question, FALSE, NULL);
711 /* Only proceed if ok to */
712 if (ok) {
714 /* If file is read only, and /A:r or /F supplied, delete it */
715 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
716 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
717 strstrW (quals, parmF) != NULL)) {
718 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
721 /* Now do the delete */
722 if (!DeleteFileW(fpath)) WCMD_print_error ();
726 } while (FindNextFileW(hff, &fd) != 0);
727 FindClose (hff);
730 /* Now recurse into all subdirectories handling the parameter in the same way */
731 if (strstrW (quals, parmS) != NULL) {
733 WCHAR thisDir[MAX_PATH];
734 int cPos;
736 WCHAR drive[10];
737 WCHAR dir[MAX_PATH];
738 WCHAR fname[MAX_PATH];
739 WCHAR ext[MAX_PATH];
741 /* Convert path into actual directory spec */
742 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
743 WCMD_splitpath(thisDir, drive, dir, fname, ext);
745 strcpyW(thisDir, drive);
746 strcatW(thisDir, dir);
747 cPos = strlenW(thisDir);
749 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
751 /* Append '*' to the directory */
752 thisDir[cPos] = '*';
753 thisDir[cPos+1] = 0x00;
755 hff = FindFirstFileW(thisDir, &fd);
757 /* Remove residual '*' */
758 thisDir[cPos] = 0x00;
760 if (hff != INVALID_HANDLE_VALUE) {
761 DIRECTORY_STACK *allDirs = NULL;
762 DIRECTORY_STACK *lastEntry = NULL;
764 do {
765 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
766 (strcmpW(fd.cFileName, dotdotW) != 0) &&
767 (strcmpW(fd.cFileName, dotW) != 0)) {
769 DIRECTORY_STACK *nextDir;
770 WCHAR subParm[MAX_PATH];
772 /* Work out search parameter in sub dir */
773 strcpyW (subParm, thisDir);
774 strcatW (subParm, fd.cFileName);
775 strcatW (subParm, slashW);
776 strcatW (subParm, fname);
777 strcatW (subParm, ext);
778 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
780 /* Allocate memory, add to list */
781 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
782 if (allDirs == NULL) allDirs = nextDir;
783 if (lastEntry != NULL) lastEntry->next = nextDir;
784 lastEntry = nextDir;
785 nextDir->next = NULL;
786 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
787 (strlenW(subParm)+1) * sizeof(WCHAR));
788 strcpyW(nextDir->dirName, subParm);
790 } while (FindNextFileW(hff, &fd) != 0);
791 FindClose (hff);
793 /* Go through each subdir doing the delete */
794 while (allDirs != NULL) {
795 DIRECTORY_STACK *tempDir;
797 tempDir = allDirs->next;
798 found |= WCMD_delete_one (allDirs->dirName);
800 HeapFree(GetProcessHeap(),0,allDirs->dirName);
801 HeapFree(GetProcessHeap(),0,allDirs);
802 allDirs = tempDir;
807 return found;
810 /****************************************************************************
811 * WCMD_delete
813 * Delete a file or wildcarded set.
815 * Note on /A:
816 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
817 * - Each set is a pattern, eg /ahr /as-r means
818 * readonly+hidden OR nonreadonly system files
819 * - The '-' applies to a single field, ie /a:-hr means read only
820 * non-hidden files
823 BOOL WCMD_delete (WCHAR *command) {
824 int argno;
825 WCHAR *argN;
826 BOOL argsProcessed = FALSE;
827 BOOL foundAny = FALSE;
829 errorlevel = 0;
831 for (argno=0; ; argno++) {
832 BOOL found;
833 WCHAR *thisArg;
835 argN = NULL;
836 thisArg = WCMD_parameter (command, argno, &argN, NULL);
837 if (!argN)
838 break; /* no more parameters */
839 if (argN[0] == '/')
840 continue; /* skip options */
842 argsProcessed = TRUE;
843 found = WCMD_delete_one(thisArg);
844 if (!found) {
845 errorlevel = 1;
846 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
848 foundAny |= found;
851 /* Handle no valid args */
852 if (!argsProcessed)
853 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
855 return foundAny;
858 /****************************************************************************
859 * WCMD_echo
861 * Echo input to the screen (or not). We don't try to emulate the bugs
862 * in DOS (try typing "ECHO ON AGAIN" for an example).
865 void WCMD_echo (const WCHAR *command) {
867 int count;
868 const WCHAR *origcommand = command;
870 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
871 || command[0]==':' || command[0]==';')
872 command++;
874 count = strlenW(command);
875 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
876 && origcommand[0]!=';') {
877 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
878 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
879 return;
881 if (lstrcmpiW(command, onW) == 0) {
882 echo_mode = TRUE;
883 return;
885 if (lstrcmpiW(command, offW) == 0) {
886 echo_mode = FALSE;
887 return;
889 WCMD_output_asis (command);
890 WCMD_output (newline);
894 /**************************************************************************
895 * WCMD_for
897 * Batch file loop processing.
899 * On entry: cmdList contains the syntax up to the set
900 * next cmdList and all in that bracket contain the set data
901 * next cmdlist contains the DO cmd
902 * following that is either brackets or && entries (as per if)
906 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
908 WIN32_FIND_DATAW fd;
909 HANDLE hff;
910 int i;
911 static const WCHAR inW[] = {'i','n'};
912 static const WCHAR doW[] = {'d','o'};
913 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
914 WCHAR variable[4];
915 WCHAR *firstCmd;
916 int thisDepth;
918 WCHAR *curPos = p;
919 BOOL expandDirs = FALSE;
920 BOOL useNumbers = FALSE;
921 BOOL doFileset = FALSE;
922 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
923 int itemNum;
924 CMD_LIST *thisCmdStart;
927 /* Handle optional qualifiers (multiple are allowed) */
928 while (*curPos && *curPos == '/') {
929 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
930 curPos++;
931 switch (toupperW(*curPos)) {
932 case 'D': curPos++; expandDirs = TRUE; break;
933 case 'L': curPos++; useNumbers = TRUE; break;
935 /* Recursive is special case - /R can have an optional path following it */
936 /* filenamesets are another special case - /F can have an optional options following it */
937 case 'R':
938 case 'F':
940 BOOL isRecursive = (*curPos == 'R');
942 if (!isRecursive)
943 doFileset = TRUE;
945 /* Skip whitespace */
946 curPos++;
947 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
949 /* Next parm is either qualifier, path/options or variable -
950 only care about it if it is the path/options */
951 if (*curPos && *curPos != '/' && *curPos != '%') {
952 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
953 else {
954 static unsigned int once;
955 if (!once++) WINE_FIXME("/F needs to handle options\n");
958 break;
960 default:
961 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
962 curPos++;
965 /* Skip whitespace between qualifiers */
966 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
969 /* Skip whitespace before variable */
970 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
972 /* Ensure line continues with variable */
973 if (!*curPos || *curPos != '%') {
974 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
975 return;
978 /* Variable should follow */
979 i = 0;
980 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
981 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
982 variable[i] = 0x00;
983 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
984 curPos = &curPos[i];
986 /* Skip whitespace before IN */
987 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
989 /* Ensure line continues with IN */
990 if (!*curPos
991 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
993 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
994 return;
997 /* Save away where the set of data starts and the variable */
998 thisDepth = (*cmdList)->bracketDepth;
999 *cmdList = (*cmdList)->nextcommand;
1000 setStart = (*cmdList);
1002 /* Skip until the close bracket */
1003 WINE_TRACE("Searching %p as the set\n", *cmdList);
1004 while (*cmdList &&
1005 (*cmdList)->command != NULL &&
1006 (*cmdList)->bracketDepth > thisDepth) {
1007 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1008 *cmdList = (*cmdList)->nextcommand;
1011 /* Skip the close bracket, if there is one */
1012 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1014 /* Syntax error if missing close bracket, or nothing following it
1015 and once we have the complete set, we expect a DO */
1016 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1017 if ((*cmdList == NULL)
1018 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1020 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1021 return;
1024 /* Save away the starting position for the commands (and offset for the
1025 first one */
1026 cmdStart = *cmdList;
1027 cmdEnd = *cmdList;
1028 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1029 itemNum = 0;
1031 thisSet = setStart;
1032 /* Loop through all set entries */
1033 while (thisSet &&
1034 thisSet->command != NULL &&
1035 thisSet->bracketDepth >= thisDepth) {
1037 /* Loop through all entries on the same line */
1038 WCHAR *item;
1039 WCHAR *itemStart;
1041 WINE_TRACE("Processing for set %p\n", thisSet);
1042 i = 0;
1043 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1046 * If the parameter within the set has a wildcard then search for matching files
1047 * otherwise do a literal substitution.
1049 static const WCHAR wildcards[] = {'*','?','\0'};
1050 thisCmdStart = cmdStart;
1052 itemNum++;
1053 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1055 if (!useNumbers && !doFileset) {
1056 if (strpbrkW (item, wildcards)) {
1057 hff = FindFirstFileW(item, &fd);
1058 if (hff != INVALID_HANDLE_VALUE) {
1059 do {
1060 BOOL isDirectory = FALSE;
1062 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1064 /* Handle as files or dirs appropriately, but ignore . and .. */
1065 if (isDirectory == expandDirs &&
1066 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1067 (strcmpW(fd.cFileName, dotW) != 0))
1069 thisCmdStart = cmdStart;
1070 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1071 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1072 fd.cFileName, FALSE, TRUE);
1075 } while (FindNextFileW(hff, &fd) != 0);
1076 FindClose (hff);
1078 } else {
1079 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1082 } else if (useNumbers) {
1083 /* Convert the first 3 numbers to signed longs and save */
1084 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1085 /* else ignore them! */
1087 /* Filesets - either a list of files, or a command to run and parse the output */
1088 } else if (doFileset && *itemStart != '"') {
1090 HANDLE input;
1091 WCHAR temp_file[MAX_PATH];
1093 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1094 wine_dbgstr_w(item));
1096 /* If backquote or single quote, we need to launch that command
1097 and parse the results - use a temporary file */
1098 if (*itemStart == '`' || *itemStart == '\'') {
1100 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1101 static const WCHAR redirOut[] = {'>','%','s','\0'};
1102 static const WCHAR cmdW[] = {'C','M','D','\0'};
1104 /* Remove trailing character */
1105 itemStart[strlenW(itemStart)-1] = 0x00;
1107 /* Get temp filename */
1108 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1109 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1111 /* Execute program and redirect output */
1112 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1113 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1115 /* Open the file, read line by line and process */
1116 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1117 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1118 } else {
1120 /* Open the file, read line by line and process */
1121 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1122 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1125 /* Process the input file */
1126 if (input == INVALID_HANDLE_VALUE) {
1127 WCMD_print_error ();
1128 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1129 errorlevel = 1;
1130 return; /* FOR loop aborts at first failure here */
1132 } else {
1134 WCHAR buffer[MAXSTRING] = {'\0'};
1135 WCHAR *where, *parm;
1137 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1139 /* Skip blank lines*/
1140 parm = WCMD_parameter (buffer, 0, &where, NULL);
1141 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1142 wine_dbgstr_w(buffer));
1144 if (where) {
1145 /* FIXME: The following should be moved into its own routine and
1146 reused for the string literal parsing below */
1147 thisCmdStart = cmdStart;
1148 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1149 cmdEnd = thisCmdStart;
1152 buffer[0] = 0x00;
1155 CloseHandle (input);
1158 /* Delete the temporary file */
1159 if (*itemStart == '`' || *itemStart == '\'') {
1160 DeleteFileW(temp_file);
1163 /* Filesets - A string literal */
1164 } else if (doFileset && *itemStart == '"') {
1165 WCHAR buffer[MAXSTRING] = {'\0'};
1166 WCHAR *where, *parm;
1168 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1169 strcpyW(buffer, item);
1170 parm = WCMD_parameter (buffer, 0, &where, NULL);
1171 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1172 wine_dbgstr_w(buffer));
1174 if (where) {
1175 /* FIXME: The following should be moved into its own routine and
1176 reused for the string literal parsing below */
1177 thisCmdStart = cmdStart;
1178 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1179 cmdEnd = thisCmdStart;
1183 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1184 cmdEnd = thisCmdStart;
1185 i++;
1188 /* Move onto the next set line */
1189 thisSet = thisSet->nextcommand;
1192 /* If /L is provided, now run the for loop */
1193 if (useNumbers) {
1194 WCHAR thisNum[20];
1195 static const WCHAR fmt[] = {'%','d','\0'};
1197 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1198 numbers[0], numbers[2], numbers[1]);
1199 for (i=numbers[0];
1200 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1201 i=i + numbers[1]) {
1203 sprintfW(thisNum, fmt, i);
1204 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1206 thisCmdStart = cmdStart;
1207 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1208 cmdEnd = thisCmdStart;
1212 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1213 all processing, OR it should be pointing to the end of && processing OR
1214 it should be pointing at the NULL end of bracket for the DO. The return
1215 value needs to be the NEXT command to execute, which it either is, or
1216 we need to step over the closing bracket */
1217 *cmdList = cmdEnd;
1218 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1222 /*****************************************************************************
1223 * WCMD_part_execute
1225 * Execute a command, and any && or bracketed follow on to the command. The
1226 * first command to be executed may not be at the front of the
1227 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1229 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1230 const WCHAR *variable, const WCHAR *value,
1231 BOOL isIF, BOOL conditionTRUE) {
1233 CMD_LIST *curPosition = *cmdList;
1234 int myDepth = (*cmdList)->bracketDepth;
1236 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1237 cmdList, wine_dbgstr_w(firstcmd),
1238 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1239 conditionTRUE);
1241 /* Skip leading whitespace between condition and the command */
1242 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1244 /* Process the first command, if there is one */
1245 if (conditionTRUE && firstcmd && *firstcmd) {
1246 WCHAR *command = WCMD_strdupW(firstcmd);
1247 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1248 HeapFree(GetProcessHeap(), 0, command);
1252 /* If it didn't move the position, step to next command */
1253 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1255 /* Process any other parts of the command */
1256 if (*cmdList) {
1257 BOOL processThese = TRUE;
1259 if (isIF) processThese = conditionTRUE;
1261 while (*cmdList) {
1262 static const WCHAR ifElse[] = {'e','l','s','e'};
1264 /* execute all appropriate commands */
1265 curPosition = *cmdList;
1267 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1268 *cmdList,
1269 (*cmdList)->prevDelim,
1270 (*cmdList)->bracketDepth, myDepth);
1272 /* Execute any statements appended to the line */
1273 /* FIXME: Only if previous call worked for && or failed for || */
1274 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1275 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1276 if (processThese && (*cmdList)->command) {
1277 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1278 value, cmdList);
1280 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1282 /* Execute any appended to the statement with (...) */
1283 } else if ((*cmdList)->bracketDepth > myDepth) {
1284 if (processThese) {
1285 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1286 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1288 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1290 /* End of the command - does 'ELSE ' follow as the next command? */
1291 } else {
1292 if (isIF
1293 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1294 (*cmdList)->command)) {
1296 /* Swap between if and else processing */
1297 processThese = !processThese;
1299 /* Process the ELSE part */
1300 if (processThese) {
1301 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1302 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1304 /* Skip leading whitespace between condition and the command */
1305 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1306 if (*cmd) {
1307 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1310 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1311 } else {
1312 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1313 break;
1318 return;
1321 /**************************************************************************
1322 * WCMD_give_help
1324 * Simple on-line help. Help text is stored in the resource file.
1327 void WCMD_give_help (const WCHAR *command) {
1329 int i;
1331 command = WCMD_skip_leading_spaces((WCHAR*) command);
1332 if (strlenW(command) == 0) {
1333 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1335 else {
1336 /* Display help message for builtin commands */
1337 for (i=0; i<=WCMD_EXIT; i++) {
1338 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1339 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1340 WCMD_output_asis (WCMD_LoadMessage(i));
1341 return;
1344 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1345 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1346 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1347 command, -1, externals[i], -1) == CSTR_EQUAL) {
1348 WCHAR cmd[128];
1349 static const WCHAR helpW[] = {' ', '/','?','\0'};
1350 strcpyW(cmd, command);
1351 strcatW(cmd, helpW);
1352 WCMD_run_program(cmd, 0);
1353 return;
1356 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1358 return;
1361 /****************************************************************************
1362 * WCMD_go_to
1364 * Batch file jump instruction. Not the most efficient algorithm ;-)
1365 * Prints error message if the specified label cannot be found - the file pointer is
1366 * then at EOF, effectively stopping the batch file.
1367 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1370 void WCMD_goto (CMD_LIST **cmdList) {
1372 WCHAR string[MAX_PATH];
1373 WCHAR current[MAX_PATH];
1375 /* Do not process any more parts of a processed multipart or multilines command */
1376 if (cmdList) *cmdList = NULL;
1378 if (context != NULL) {
1379 WCHAR *paramStart = param1, *str;
1380 static const WCHAR eofW[] = {':','e','o','f','\0'};
1382 if (param1[0] == 0x00) {
1383 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1384 return;
1387 /* Handle special :EOF label */
1388 if (lstrcmpiW (eofW, param1) == 0) {
1389 context -> skip_rest = TRUE;
1390 return;
1393 /* Support goto :label as well as goto label */
1394 if (*paramStart == ':') paramStart++;
1396 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1397 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1398 str = string;
1399 while (isspaceW (*str)) str++;
1400 if (*str == ':') {
1401 DWORD index = 0;
1402 str++;
1403 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1404 index++;
1406 /* ignore space at the end */
1407 current[index] = 0;
1408 if (lstrcmpiW (current, paramStart) == 0) return;
1411 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1413 return;
1416 /*****************************************************************************
1417 * WCMD_pushd
1419 * Push a directory onto the stack
1422 void WCMD_pushd (WCHAR *command) {
1423 struct env_stack *curdir;
1424 WCHAR *thisdir;
1425 static const WCHAR parmD[] = {'/','D','\0'};
1427 if (strchrW(command, '/') != NULL) {
1428 SetLastError(ERROR_INVALID_PARAMETER);
1429 WCMD_print_error();
1430 return;
1433 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1434 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1435 if( !curdir || !thisdir ) {
1436 LocalFree(curdir);
1437 LocalFree(thisdir);
1438 WINE_ERR ("out of memory\n");
1439 return;
1442 /* Change directory using CD code with /D parameter */
1443 strcpyW(quals, parmD);
1444 GetCurrentDirectoryW (1024, thisdir);
1445 errorlevel = 0;
1446 WCMD_setshow_default(command);
1447 if (errorlevel) {
1448 LocalFree(curdir);
1449 LocalFree(thisdir);
1450 return;
1451 } else {
1452 curdir -> next = pushd_directories;
1453 curdir -> strings = thisdir;
1454 if (pushd_directories == NULL) {
1455 curdir -> u.stackdepth = 1;
1456 } else {
1457 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1459 pushd_directories = curdir;
1464 /*****************************************************************************
1465 * WCMD_popd
1467 * Pop a directory from the stack
1470 void WCMD_popd (void) {
1471 struct env_stack *temp = pushd_directories;
1473 if (!pushd_directories)
1474 return;
1476 /* pop the old environment from the stack, and make it the current dir */
1477 pushd_directories = temp->next;
1478 SetCurrentDirectoryW(temp->strings);
1479 LocalFree (temp->strings);
1480 LocalFree (temp);
1483 /****************************************************************************
1484 * WCMD_if
1486 * Batch file conditional.
1488 * On entry, cmdlist will point to command containing the IF, and optionally
1489 * the first command to execute (if brackets not found)
1490 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1491 * If ('s were found, execute all within that bracket
1492 * Command may optionally be followed by an ELSE - need to skip instructions
1493 * in the else using the same logic
1495 * FIXME: Much more syntax checking needed!
1498 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1500 int negate; /* Negate condition */
1501 int test; /* Condition evaluation result */
1502 WCHAR condition[MAX_PATH], *command, *s;
1503 static const WCHAR notW[] = {'n','o','t','\0'};
1504 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1505 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1506 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1507 static const WCHAR eqeqW[] = {'=','=','\0'};
1508 static const WCHAR parmI[] = {'/','I','\0'};
1509 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1511 negate = !lstrcmpiW(param1,notW);
1512 strcpyW(condition, (negate ? param2 : param1));
1513 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1515 if (!lstrcmpiW (condition, errlvlW)) {
1516 test = (errorlevel >= atoiW(WCMD_parameter(p, 1+negate, NULL, NULL)));
1517 WCMD_parameter(p, 2+negate, &command, NULL);
1519 else if (!lstrcmpiW (condition, existW)) {
1520 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1521 WCMD_parameter(p, 2+negate, &command, NULL);
1523 else if (!lstrcmpiW (condition, defdW)) {
1524 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1525 WCMD_parameter(p, 2+negate, &command, NULL);
1527 else if ((s = strstrW (p, eqeqW))) {
1528 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1529 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1530 s += 2;
1531 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1532 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1533 test = caseInsensitive
1534 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1535 leftPart, leftPartEnd-leftPart+1,
1536 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1537 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1538 leftPart, leftPartEnd-leftPart+1,
1539 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1540 WCMD_parameter(s, 1, &command, NULL);
1542 else {
1543 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1544 return;
1547 /* Process rest of IF statement which is on the same line
1548 Note: This may process all or some of the cmdList (eg a GOTO) */
1549 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1552 /****************************************************************************
1553 * WCMD_move
1555 * Move a file, directory tree or wildcarded set of files.
1558 void WCMD_move (void) {
1560 int status;
1561 WIN32_FIND_DATAW fd;
1562 HANDLE hff;
1563 WCHAR input[MAX_PATH];
1564 WCHAR output[MAX_PATH];
1565 WCHAR drive[10];
1566 WCHAR dir[MAX_PATH];
1567 WCHAR fname[MAX_PATH];
1568 WCHAR ext[MAX_PATH];
1570 if (param1[0] == 0x00) {
1571 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1572 return;
1575 /* If no destination supplied, assume current directory */
1576 if (param2[0] == 0x00) {
1577 strcpyW(param2, dotW);
1580 /* If 2nd parm is directory, then use original filename */
1581 /* Convert partial path to full path */
1582 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1583 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1584 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1585 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1587 /* Split into components */
1588 WCMD_splitpath(input, drive, dir, fname, ext);
1590 hff = FindFirstFileW(input, &fd);
1591 while (hff != INVALID_HANDLE_VALUE) {
1592 WCHAR dest[MAX_PATH];
1593 WCHAR src[MAX_PATH];
1594 DWORD attribs;
1596 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1598 /* Build src & dest name */
1599 strcpyW(src, drive);
1600 strcatW(src, dir);
1602 /* See if dest is an existing directory */
1603 attribs = GetFileAttributesW(output);
1604 if (attribs != INVALID_FILE_ATTRIBUTES &&
1605 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1606 strcpyW(dest, output);
1607 strcatW(dest, slashW);
1608 strcatW(dest, fd.cFileName);
1609 } else {
1610 strcpyW(dest, output);
1613 strcatW(src, fd.cFileName);
1615 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1616 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1618 /* Check if file is read only, otherwise move it */
1619 attribs = GetFileAttributesW(src);
1620 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1621 (attribs & FILE_ATTRIBUTE_READONLY)) {
1622 SetLastError(ERROR_ACCESS_DENIED);
1623 status = 0;
1624 } else {
1625 BOOL ok = TRUE;
1627 /* If destination exists, prompt unless /Y supplied */
1628 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1629 BOOL force = FALSE;
1630 WCHAR copycmd[MAXSTRING];
1631 int len;
1633 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1634 if (strstrW (quals, parmNoY))
1635 force = FALSE;
1636 else if (strstrW (quals, parmY))
1637 force = TRUE;
1638 else {
1639 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1640 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1641 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1642 && ! lstrcmpiW (copycmd, parmY));
1645 /* Prompt if overwriting */
1646 if (!force) {
1647 WCHAR question[MAXSTRING];
1648 WCHAR yesChar[10];
1650 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1652 /* Ask for confirmation */
1653 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1654 ok = WCMD_ask_confirm(question, FALSE, NULL);
1656 /* So delete the destination prior to the move */
1657 if (ok) {
1658 if (!DeleteFileW(dest)) {
1659 WCMD_print_error ();
1660 errorlevel = 1;
1661 ok = FALSE;
1667 if (ok) {
1668 status = MoveFileW(src, dest);
1669 } else {
1670 status = 1; /* Anything other than 0 to prevent error msg below */
1674 if (!status) {
1675 WCMD_print_error ();
1676 errorlevel = 1;
1679 /* Step on to next match */
1680 if (FindNextFileW(hff, &fd) == 0) {
1681 FindClose(hff);
1682 hff = INVALID_HANDLE_VALUE;
1683 break;
1688 /****************************************************************************
1689 * WCMD_pause
1691 * Wait for keyboard input.
1694 void WCMD_pause (void) {
1696 DWORD count;
1697 WCHAR string[32];
1699 WCMD_output (anykey);
1700 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1701 sizeof(string)/sizeof(WCHAR), &count, NULL);
1704 /****************************************************************************
1705 * WCMD_remove_dir
1707 * Delete a directory.
1710 void WCMD_remove_dir (WCHAR *command) {
1712 int argno = 0;
1713 int argsProcessed = 0;
1714 WCHAR *argN = command;
1715 static const WCHAR parmS[] = {'/','S','\0'};
1716 static const WCHAR parmQ[] = {'/','Q','\0'};
1718 /* Loop through all args */
1719 while (argN) {
1720 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1721 if (argN && argN[0] != '/') {
1722 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1723 wine_dbgstr_w(quals));
1724 argsProcessed++;
1726 /* If subdirectory search not supplied, just try to remove
1727 and report error if it fails (eg if it contains a file) */
1728 if (strstrW (quals, parmS) == NULL) {
1729 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1731 /* Otherwise use ShFileOp to recursively remove a directory */
1732 } else {
1734 SHFILEOPSTRUCTW lpDir;
1736 /* Ask first */
1737 if (strstrW (quals, parmQ) == NULL) {
1738 BOOL ok;
1739 WCHAR question[MAXSTRING];
1740 static const WCHAR fmt[] = {'%','s',' ','\0'};
1742 /* Ask for confirmation */
1743 wsprintfW(question, fmt, thisArg);
1744 ok = WCMD_ask_confirm(question, TRUE, NULL);
1746 /* Abort if answer is 'N' */
1747 if (!ok) return;
1750 /* Do the delete */
1751 lpDir.hwnd = NULL;
1752 lpDir.pTo = NULL;
1753 lpDir.pFrom = thisArg;
1754 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1755 lpDir.wFunc = FO_DELETE;
1756 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1761 /* Handle no valid args */
1762 if (argsProcessed == 0) {
1763 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1764 return;
1769 /****************************************************************************
1770 * WCMD_rename
1772 * Rename a file.
1775 void WCMD_rename (void) {
1777 int status;
1778 HANDLE hff;
1779 WIN32_FIND_DATAW fd;
1780 WCHAR input[MAX_PATH];
1781 WCHAR *dotDst = NULL;
1782 WCHAR drive[10];
1783 WCHAR dir[MAX_PATH];
1784 WCHAR fname[MAX_PATH];
1785 WCHAR ext[MAX_PATH];
1786 DWORD attribs;
1788 errorlevel = 0;
1790 /* Must be at least two args */
1791 if (param1[0] == 0x00 || param2[0] == 0x00) {
1792 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1793 errorlevel = 1;
1794 return;
1797 /* Destination cannot contain a drive letter or directory separator */
1798 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1799 SetLastError(ERROR_INVALID_PARAMETER);
1800 WCMD_print_error();
1801 errorlevel = 1;
1802 return;
1805 /* Convert partial path to full path */
1806 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1807 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1808 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1809 dotDst = strchrW(param2, '.');
1811 /* Split into components */
1812 WCMD_splitpath(input, drive, dir, fname, ext);
1814 hff = FindFirstFileW(input, &fd);
1815 while (hff != INVALID_HANDLE_VALUE) {
1816 WCHAR dest[MAX_PATH];
1817 WCHAR src[MAX_PATH];
1818 WCHAR *dotSrc = NULL;
1819 int dirLen;
1821 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1823 /* FIXME: If dest name or extension is *, replace with filename/ext
1824 part otherwise use supplied name. This supports:
1825 ren *.fred *.jim
1826 ren jim.* fred.* etc
1827 However, windows has a more complex algorithm supporting eg
1828 ?'s and *'s mid name */
1829 dotSrc = strchrW(fd.cFileName, '.');
1831 /* Build src & dest name */
1832 strcpyW(src, drive);
1833 strcatW(src, dir);
1834 strcpyW(dest, src);
1835 dirLen = strlenW(src);
1836 strcatW(src, fd.cFileName);
1838 /* Build name */
1839 if (param2[0] == '*') {
1840 strcatW(dest, fd.cFileName);
1841 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1842 } else {
1843 strcatW(dest, param2);
1844 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1847 /* Build Extension */
1848 if (dotDst && (*(dotDst+1)=='*')) {
1849 if (dotSrc) strcatW(dest, dotSrc);
1850 } else if (dotDst) {
1851 if (dotDst) strcatW(dest, dotDst);
1854 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1855 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1857 /* Check if file is read only, otherwise move it */
1858 attribs = GetFileAttributesW(src);
1859 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1860 (attribs & FILE_ATTRIBUTE_READONLY)) {
1861 SetLastError(ERROR_ACCESS_DENIED);
1862 status = 0;
1863 } else {
1864 status = MoveFileW(src, dest);
1867 if (!status) {
1868 WCMD_print_error ();
1869 errorlevel = 1;
1872 /* Step on to next match */
1873 if (FindNextFileW(hff, &fd) == 0) {
1874 FindClose(hff);
1875 hff = INVALID_HANDLE_VALUE;
1876 break;
1881 /*****************************************************************************
1882 * WCMD_dupenv
1884 * Make a copy of the environment.
1886 static WCHAR *WCMD_dupenv( const WCHAR *env )
1888 WCHAR *env_copy;
1889 int len;
1891 if( !env )
1892 return NULL;
1894 len = 0;
1895 while ( env[len] )
1896 len += (strlenW(&env[len]) + 1);
1898 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1899 if (!env_copy)
1901 WINE_ERR("out of memory\n");
1902 return env_copy;
1904 memcpy (env_copy, env, len*sizeof (WCHAR));
1905 env_copy[len] = 0;
1907 return env_copy;
1910 /*****************************************************************************
1911 * WCMD_setlocal
1913 * setlocal pushes the environment onto a stack
1914 * Save the environment as unicode so we don't screw anything up.
1916 void WCMD_setlocal (const WCHAR *s) {
1917 WCHAR *env;
1918 struct env_stack *env_copy;
1919 WCHAR cwd[MAX_PATH];
1921 /* DISABLEEXTENSIONS ignored */
1923 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1924 if( !env_copy )
1926 WINE_ERR ("out of memory\n");
1927 return;
1930 env = GetEnvironmentStringsW ();
1932 env_copy->strings = WCMD_dupenv (env);
1933 if (env_copy->strings)
1935 env_copy->next = saved_environment;
1936 saved_environment = env_copy;
1938 /* Save the current drive letter */
1939 GetCurrentDirectoryW(MAX_PATH, cwd);
1940 env_copy->u.cwd = cwd[0];
1942 else
1943 LocalFree (env_copy);
1945 FreeEnvironmentStringsW (env);
1949 /*****************************************************************************
1950 * WCMD_endlocal
1952 * endlocal pops the environment off a stack
1953 * Note: When searching for '=', search from WCHAR position 1, to handle
1954 * special internal environment variables =C:, =D: etc
1956 void WCMD_endlocal (void) {
1957 WCHAR *env, *old, *p;
1958 struct env_stack *temp;
1959 int len, n;
1961 if (!saved_environment)
1962 return;
1964 /* pop the old environment from the stack */
1965 temp = saved_environment;
1966 saved_environment = temp->next;
1968 /* delete the current environment, totally */
1969 env = GetEnvironmentStringsW ();
1970 old = WCMD_dupenv (GetEnvironmentStringsW ());
1971 len = 0;
1972 while (old[len]) {
1973 n = strlenW(&old[len]) + 1;
1974 p = strchrW(&old[len] + 1, '=');
1975 if (p)
1977 *p++ = 0;
1978 SetEnvironmentVariableW (&old[len], NULL);
1980 len += n;
1982 LocalFree (old);
1983 FreeEnvironmentStringsW (env);
1985 /* restore old environment */
1986 env = temp->strings;
1987 len = 0;
1988 while (env[len]) {
1989 n = strlenW(&env[len]) + 1;
1990 p = strchrW(&env[len] + 1, '=');
1991 if (p)
1993 *p++ = 0;
1994 SetEnvironmentVariableW (&env[len], p);
1996 len += n;
1999 /* Restore current drive letter */
2000 if (IsCharAlphaW(temp->u.cwd)) {
2001 WCHAR envvar[4];
2002 WCHAR cwd[MAX_PATH];
2003 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2005 wsprintfW(envvar, fmt, temp->u.cwd);
2006 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2007 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2008 SetCurrentDirectoryW(cwd);
2012 LocalFree (env);
2013 LocalFree (temp);
2016 /*****************************************************************************
2017 * WCMD_setshow_default
2019 * Set/Show the current default directory
2022 void WCMD_setshow_default (const WCHAR *command) {
2024 BOOL status;
2025 WCHAR string[1024];
2026 WCHAR cwd[1024];
2027 WCHAR *pos;
2028 WIN32_FIND_DATAW fd;
2029 HANDLE hff;
2030 static const WCHAR parmD[] = {'/','D','\0'};
2032 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2034 /* Skip /D and trailing whitespace if on the front of the command line */
2035 if (CompareStringW(LOCALE_USER_DEFAULT,
2036 NORM_IGNORECASE | SORT_STRINGSORT,
2037 command, 2, parmD, -1) == CSTR_EQUAL) {
2038 command += 2;
2039 while (*command && (*command==' ' || *command=='\t'))
2040 command++;
2043 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2044 if (strlenW(command) == 0) {
2045 strcatW (cwd, newline);
2046 WCMD_output (cwd);
2048 else {
2049 /* Remove any double quotes, which may be in the
2050 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2051 pos = string;
2052 while (*command) {
2053 if (*command != '"') *pos++ = *command;
2054 command++;
2056 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2057 pos--;
2058 *pos = 0x00;
2060 /* Search for appropriate directory */
2061 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2062 hff = FindFirstFileW(string, &fd);
2063 while (hff != INVALID_HANDLE_VALUE) {
2064 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2065 WCHAR fpath[MAX_PATH];
2066 WCHAR drive[10];
2067 WCHAR dir[MAX_PATH];
2068 WCHAR fname[MAX_PATH];
2069 WCHAR ext[MAX_PATH];
2070 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2072 /* Convert path into actual directory spec */
2073 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2074 WCMD_splitpath(fpath, drive, dir, fname, ext);
2076 /* Rebuild path */
2077 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2079 FindClose(hff);
2080 hff = INVALID_HANDLE_VALUE;
2081 break;
2084 /* Step on to next match */
2085 if (FindNextFileW(hff, &fd) == 0) {
2086 FindClose(hff);
2087 hff = INVALID_HANDLE_VALUE;
2088 break;
2092 /* Change to that directory */
2093 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2095 status = SetCurrentDirectoryW(string);
2096 if (!status) {
2097 errorlevel = 1;
2098 WCMD_print_error ();
2099 return;
2100 } else {
2102 /* Save away the actual new directory, to store as current location */
2103 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2105 /* Restore old directory if drive letter would change, and
2106 CD x:\directory /D (or pushd c:\directory) not supplied */
2107 if ((strstrW(quals, parmD) == NULL) &&
2108 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2109 SetCurrentDirectoryW(cwd);
2113 /* Set special =C: type environment variable, for drive letter of
2114 change of directory, even if path was restored due to missing
2115 /D (allows changing drive letter when not resident on that
2116 drive */
2117 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2118 WCHAR env[4];
2119 strcpyW(env, equalW);
2120 memcpy(env+1, string, 2 * sizeof(WCHAR));
2121 env[3] = 0x00;
2122 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2123 SetEnvironmentVariableW(env, string);
2127 return;
2130 /****************************************************************************
2131 * WCMD_setshow_date
2133 * Set/Show the system date
2134 * FIXME: Can't change date yet
2137 void WCMD_setshow_date (void) {
2139 WCHAR curdate[64], buffer[64];
2140 DWORD count;
2141 static const WCHAR parmT[] = {'/','T','\0'};
2143 if (strlenW(param1) == 0) {
2144 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2145 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2146 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2147 if (strstrW (quals, parmT) == NULL) {
2148 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2149 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2150 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2151 if (count > 2) {
2152 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2156 else WCMD_print_error ();
2158 else {
2159 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2163 /****************************************************************************
2164 * WCMD_compare
2166 static int WCMD_compare( const void *a, const void *b )
2168 int r;
2169 const WCHAR * const *str_a = a, * const *str_b = b;
2170 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2171 *str_a, -1, *str_b, -1 );
2172 if( r == CSTR_LESS_THAN ) return -1;
2173 if( r == CSTR_GREATER_THAN ) return 1;
2174 return 0;
2177 /****************************************************************************
2178 * WCMD_setshow_sortenv
2180 * sort variables into order for display
2181 * Optionally only display those who start with a stub
2182 * returns the count displayed
2184 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2186 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2187 const WCHAR **str;
2189 if (stub) stublen = strlenW(stub);
2191 /* count the number of strings, and the total length */
2192 while ( s[len] ) {
2193 len += (strlenW(&s[len]) + 1);
2194 count++;
2197 /* add the strings to an array */
2198 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2199 if( !str )
2200 return 0;
2201 str[0] = s;
2202 for( i=1; i<count; i++ )
2203 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2205 /* sort the array */
2206 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2208 /* print it */
2209 for( i=0; i<count; i++ ) {
2210 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2211 NORM_IGNORECASE | SORT_STRINGSORT,
2212 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2213 /* Don't display special internal variables */
2214 if (str[i][0] != '=') {
2215 WCMD_output_asis(str[i]);
2216 WCMD_output_asis(newline);
2217 displayedcount++;
2222 LocalFree( str );
2223 return displayedcount;
2226 /****************************************************************************
2227 * WCMD_setshow_env
2229 * Set/Show the environment variables
2232 void WCMD_setshow_env (WCHAR *s) {
2234 LPVOID env;
2235 WCHAR *p;
2236 int status;
2237 static const WCHAR parmP[] = {'/','P','\0'};
2239 if (param1[0] == 0x00 && quals[0] == 0x00) {
2240 env = GetEnvironmentStringsW();
2241 WCMD_setshow_sortenv( env, NULL );
2242 return;
2245 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2246 if (CompareStringW(LOCALE_USER_DEFAULT,
2247 NORM_IGNORECASE | SORT_STRINGSORT,
2248 s, 2, parmP, -1) == CSTR_EQUAL) {
2249 WCHAR string[MAXSTRING];
2250 DWORD count;
2252 s += 2;
2253 while (*s && (*s==' ' || *s=='\t')) s++;
2254 if (*s=='\"')
2255 WCMD_opt_s_strip_quotes(s);
2257 /* If no parameter, or no '=' sign, return an error */
2258 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2259 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2260 return;
2263 /* Output the prompt */
2264 *p++ = '\0';
2265 if (strlenW(p) != 0) WCMD_output(p);
2267 /* Read the reply */
2268 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2269 sizeof(string)/sizeof(WCHAR), &count, NULL);
2270 if (count > 1) {
2271 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2272 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2273 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2274 wine_dbgstr_w(string));
2275 status = SetEnvironmentVariableW(s, string);
2278 } else {
2279 DWORD gle;
2281 if (*s=='\"')
2282 WCMD_opt_s_strip_quotes(s);
2283 p = strchrW (s, '=');
2284 if (p == NULL) {
2285 env = GetEnvironmentStringsW();
2286 if (WCMD_setshow_sortenv( env, s ) == 0) {
2287 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2288 errorlevel = 1;
2290 return;
2292 *p++ = '\0';
2294 if (strlenW(p) == 0) p = NULL;
2295 status = SetEnvironmentVariableW(s, p);
2296 gle = GetLastError();
2297 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2298 errorlevel = 1;
2299 } else if ((!status)) WCMD_print_error();
2303 /****************************************************************************
2304 * WCMD_setshow_path
2306 * Set/Show the path environment variable
2309 void WCMD_setshow_path (const WCHAR *command) {
2311 WCHAR string[1024];
2312 DWORD status;
2313 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2314 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2316 if (strlenW(param1) == 0) {
2317 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2318 if (status != 0) {
2319 WCMD_output_asis ( pathEqW);
2320 WCMD_output_asis ( string);
2321 WCMD_output_asis ( newline);
2323 else {
2324 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2327 else {
2328 if (*command == '=') command++; /* Skip leading '=' */
2329 status = SetEnvironmentVariableW(pathW, command);
2330 if (!status) WCMD_print_error();
2334 /****************************************************************************
2335 * WCMD_setshow_prompt
2337 * Set or show the command prompt.
2340 void WCMD_setshow_prompt (void) {
2342 WCHAR *s;
2343 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2345 if (strlenW(param1) == 0) {
2346 SetEnvironmentVariableW(promptW, NULL);
2348 else {
2349 s = param1;
2350 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2351 if (strlenW(s) == 0) {
2352 SetEnvironmentVariableW(promptW, NULL);
2354 else SetEnvironmentVariableW(promptW, s);
2358 /****************************************************************************
2359 * WCMD_setshow_time
2361 * Set/Show the system time
2362 * FIXME: Can't change time yet
2365 void WCMD_setshow_time (void) {
2367 WCHAR curtime[64], buffer[64];
2368 DWORD count;
2369 SYSTEMTIME st;
2370 static const WCHAR parmT[] = {'/','T','\0'};
2372 if (strlenW(param1) == 0) {
2373 GetLocalTime(&st);
2374 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2375 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2376 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2377 if (strstrW (quals, parmT) == NULL) {
2378 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2379 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2380 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2381 if (count > 2) {
2382 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2386 else WCMD_print_error ();
2388 else {
2389 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2393 /****************************************************************************
2394 * WCMD_shift
2396 * Shift batch parameters.
2397 * Optional /n says where to start shifting (n=0-8)
2400 void WCMD_shift (const WCHAR *command) {
2401 int start;
2403 if (context != NULL) {
2404 WCHAR *pos = strchrW(command, '/');
2405 int i;
2407 if (pos == NULL) {
2408 start = 0;
2409 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2410 start = (*(pos+1) - '0');
2411 } else {
2412 SetLastError(ERROR_INVALID_PARAMETER);
2413 WCMD_print_error();
2414 return;
2417 WINE_TRACE("Shifting variables, starting at %d\n", start);
2418 for (i=start;i<=8;i++) {
2419 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2421 context -> shift_count[9] = context -> shift_count[9] + 1;
2426 /****************************************************************************
2427 * WCMD_title
2429 * Set the console title
2431 void WCMD_title (const WCHAR *command) {
2432 SetConsoleTitleW(command);
2435 /****************************************************************************
2436 * WCMD_type
2438 * Copy a file to standard output.
2441 void WCMD_type (WCHAR *command) {
2443 int argno = 0;
2444 WCHAR *argN = command;
2445 BOOL writeHeaders = FALSE;
2447 if (param1[0] == 0x00) {
2448 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2449 return;
2452 if (param2[0] != 0x00) writeHeaders = TRUE;
2454 /* Loop through all args */
2455 errorlevel = 0;
2456 while (argN) {
2457 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2459 HANDLE h;
2460 WCHAR buffer[512];
2461 DWORD count;
2463 if (!argN) break;
2465 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2466 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2467 FILE_ATTRIBUTE_NORMAL, NULL);
2468 if (h == INVALID_HANDLE_VALUE) {
2469 WCMD_print_error ();
2470 WCMD_output(WCMD_LoadMessage(WCMD_READFAIL), thisArg); /* should be _stderr */
2471 errorlevel = 1;
2472 } else {
2473 if (writeHeaders) {
2474 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2475 WCMD_output(fmt, thisArg);
2477 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2478 if (count == 0) break; /* ReadFile reports success on EOF! */
2479 buffer[count] = 0;
2480 WCMD_output_asis (buffer);
2482 CloseHandle (h);
2487 /****************************************************************************
2488 * WCMD_more
2490 * Output either a file or stdin to screen in pages
2493 void WCMD_more (WCHAR *command) {
2495 int argno = 0;
2496 WCHAR *argN = command;
2497 WCHAR moreStr[100];
2498 WCHAR moreStrPage[100];
2499 WCHAR buffer[512];
2500 DWORD count;
2501 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2502 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2503 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2504 ')',' ','-','-','\n','\0'};
2505 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2507 /* Prefix the NLS more with '-- ', then load the text */
2508 errorlevel = 0;
2509 strcpyW(moreStr, moreStart);
2510 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2511 (sizeof(moreStr)/sizeof(WCHAR))-3);
2513 if (param1[0] == 0x00) {
2515 /* Wine implements pipes via temporary files, and hence stdin is
2516 effectively reading from the file. This means the prompts for
2517 more are satisfied by the next line from the input (file). To
2518 avoid this, ensure stdin is to the console */
2519 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2520 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2521 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2522 FILE_ATTRIBUTE_NORMAL, 0);
2523 WINE_TRACE("No parms - working probably in pipe mode\n");
2524 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2526 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2527 once you get in this bit unless due to a pipe, its going to end badly... */
2528 wsprintfW(moreStrPage, moreFmt, moreStr);
2530 WCMD_enter_paged_mode(moreStrPage);
2531 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2532 if (count == 0) break; /* ReadFile reports success on EOF! */
2533 buffer[count] = 0;
2534 WCMD_output_asis (buffer);
2536 WCMD_leave_paged_mode();
2538 /* Restore stdin to what it was */
2539 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2540 CloseHandle(hConIn);
2542 return;
2543 } else {
2544 BOOL needsPause = FALSE;
2546 /* Loop through all args */
2547 WINE_TRACE("Parms supplied - working through each file\n");
2548 WCMD_enter_paged_mode(moreStrPage);
2550 while (argN) {
2551 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2552 HANDLE h;
2554 if (!argN) break;
2556 if (needsPause) {
2558 /* Wait */
2559 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2560 WCMD_leave_paged_mode();
2561 WCMD_output_asis(moreStrPage);
2562 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2563 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2564 WCMD_enter_paged_mode(moreStrPage);
2568 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2569 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2570 FILE_ATTRIBUTE_NORMAL, NULL);
2571 if (h == INVALID_HANDLE_VALUE) {
2572 WCMD_print_error ();
2573 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2574 errorlevel = 1;
2575 } else {
2576 ULONG64 curPos = 0;
2577 ULONG64 fileLen = 0;
2578 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2580 /* Get the file size */
2581 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2582 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2584 needsPause = TRUE;
2585 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2586 if (count == 0) break; /* ReadFile reports success on EOF! */
2587 buffer[count] = 0;
2588 curPos += count;
2590 /* Update % count (would be used in WCMD_output_asis as prompt) */
2591 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2593 WCMD_output_asis (buffer);
2595 CloseHandle (h);
2599 WCMD_leave_paged_mode();
2603 /****************************************************************************
2604 * WCMD_verify
2606 * Display verify flag.
2607 * FIXME: We don't actually do anything with the verify flag other than toggle
2608 * it...
2611 void WCMD_verify (const WCHAR *command) {
2613 int count;
2615 count = strlenW(command);
2616 if (count == 0) {
2617 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2618 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2619 return;
2621 if (lstrcmpiW(command, onW) == 0) {
2622 verify_mode = TRUE;
2623 return;
2625 else if (lstrcmpiW(command, offW) == 0) {
2626 verify_mode = FALSE;
2627 return;
2629 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2632 /****************************************************************************
2633 * WCMD_version
2635 * Display version info.
2638 void WCMD_version (void) {
2640 WCMD_output (version_string);
2644 /****************************************************************************
2645 * WCMD_volume
2647 * Display volume information (set_label = FALSE)
2648 * Additionally set volume label (set_label = TRUE)
2649 * Returns 1 on success, 0 otherwise
2652 int WCMD_volume(BOOL set_label, const WCHAR *path)
2654 DWORD count, serial;
2655 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2656 BOOL status;
2658 if (strlenW(path) == 0) {
2659 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2660 if (!status) {
2661 WCMD_print_error ();
2662 return 0;
2664 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2665 &serial, NULL, NULL, NULL, 0);
2667 else {
2668 static const WCHAR fmt[] = {'%','s','\\','\0'};
2669 if ((path[1] != ':') || (strlenW(path) != 2)) {
2670 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2671 return 0;
2673 wsprintfW (curdir, fmt, path);
2674 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2675 &serial, NULL,
2676 NULL, NULL, 0);
2678 if (!status) {
2679 WCMD_print_error ();
2680 return 0;
2682 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2683 curdir[0], label, HIWORD(serial), LOWORD(serial));
2684 if (set_label) {
2685 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2686 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2687 sizeof(string)/sizeof(WCHAR), &count, NULL);
2688 if (count > 1) {
2689 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2690 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2692 if (strlenW(path) != 0) {
2693 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2695 else {
2696 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2699 return 1;
2702 /**************************************************************************
2703 * WCMD_exit
2705 * Exit either the process, or just this batch program
2709 void WCMD_exit (CMD_LIST **cmdList) {
2711 static const WCHAR parmB[] = {'/','B','\0'};
2712 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2714 if (context && lstrcmpiW(quals, parmB) == 0) {
2715 errorlevel = rc;
2716 context -> skip_rest = TRUE;
2717 *cmdList = NULL;
2718 } else {
2719 ExitProcess(rc);
2724 /*****************************************************************************
2725 * WCMD_assoc
2727 * Lists or sets file associations (assoc = TRUE)
2728 * Lists or sets file types (assoc = FALSE)
2730 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2732 HKEY key;
2733 DWORD accessOptions = KEY_READ;
2734 WCHAR *newValue;
2735 LONG rc = ERROR_SUCCESS;
2736 WCHAR keyValue[MAXSTRING];
2737 DWORD valueLen = MAXSTRING;
2738 HKEY readKey;
2739 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2740 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2742 /* See if parameter includes '=' */
2743 errorlevel = 0;
2744 newValue = strchrW(command, '=');
2745 if (newValue) accessOptions |= KEY_WRITE;
2747 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2748 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2749 accessOptions, &key) != ERROR_SUCCESS) {
2750 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2751 return;
2754 /* If no parameters then list all associations */
2755 if (*command == 0x00) {
2756 int index = 0;
2758 /* Enumerate all the keys */
2759 while (rc != ERROR_NO_MORE_ITEMS) {
2760 WCHAR keyName[MAXSTRING];
2761 DWORD nameLen;
2763 /* Find the next value */
2764 nameLen = MAXSTRING;
2765 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2767 if (rc == ERROR_SUCCESS) {
2769 /* Only interested in extension ones if assoc, or others
2770 if not assoc */
2771 if ((keyName[0] == '.' && assoc) ||
2772 (!(keyName[0] == '.') && (!assoc)))
2774 WCHAR subkey[MAXSTRING];
2775 strcpyW(subkey, keyName);
2776 if (!assoc) strcatW(subkey, shOpCmdW);
2778 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2780 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2781 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2782 WCMD_output_asis(keyName);
2783 WCMD_output_asis(equalW);
2784 /* If no default value found, leave line empty after '=' */
2785 if (rc == ERROR_SUCCESS) {
2786 WCMD_output_asis(keyValue);
2788 WCMD_output_asis(newline);
2789 RegCloseKey(readKey);
2795 } else {
2797 /* Parameter supplied - if no '=' on command line, its a query */
2798 if (newValue == NULL) {
2799 WCHAR *space;
2800 WCHAR subkey[MAXSTRING];
2802 /* Query terminates the parameter at the first space */
2803 strcpyW(keyValue, command);
2804 space = strchrW(keyValue, ' ');
2805 if (space) *space=0x00;
2807 /* Set up key name */
2808 strcpyW(subkey, keyValue);
2809 if (!assoc) strcatW(subkey, shOpCmdW);
2811 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2813 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2814 WCMD_output_asis(command);
2815 WCMD_output_asis(equalW);
2816 /* If no default value found, leave line empty after '=' */
2817 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2818 WCMD_output_asis(newline);
2819 RegCloseKey(readKey);
2821 } else {
2822 WCHAR msgbuffer[MAXSTRING];
2823 WCHAR outbuffer[MAXSTRING];
2825 /* Load the translated 'File association not found' */
2826 if (assoc) {
2827 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2828 } else {
2829 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2831 wsprintfW(outbuffer, msgbuffer, keyValue);
2832 WCMD_output_asis_stderr(outbuffer);
2833 errorlevel = 2;
2836 /* Not a query - its a set or clear of a value */
2837 } else {
2839 WCHAR subkey[MAXSTRING];
2841 /* Get pointer to new value */
2842 *newValue = 0x00;
2843 newValue++;
2845 /* Set up key name */
2846 strcpyW(subkey, command);
2847 if (!assoc) strcatW(subkey, shOpCmdW);
2849 /* If nothing after '=' then clear value - only valid for ASSOC */
2850 if (*newValue == 0x00) {
2852 if (assoc) rc = RegDeleteKeyW(key, command);
2853 if (assoc && rc == ERROR_SUCCESS) {
2854 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2856 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2857 WCMD_print_error();
2858 errorlevel = 2;
2860 } else {
2861 WCHAR msgbuffer[MAXSTRING];
2862 WCHAR outbuffer[MAXSTRING];
2864 /* Load the translated 'File association not found' */
2865 if (assoc) {
2866 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2867 sizeof(msgbuffer)/sizeof(WCHAR));
2868 } else {
2869 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2870 sizeof(msgbuffer)/sizeof(WCHAR));
2872 wsprintfW(outbuffer, msgbuffer, keyValue);
2873 WCMD_output_asis_stderr(outbuffer);
2874 errorlevel = 2;
2877 /* It really is a set value = contents */
2878 } else {
2879 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2880 accessOptions, NULL, &readKey, NULL);
2881 if (rc == ERROR_SUCCESS) {
2882 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2883 (LPBYTE)newValue,
2884 sizeof(WCHAR) * (strlenW(newValue) + 1));
2885 RegCloseKey(readKey);
2888 if (rc != ERROR_SUCCESS) {
2889 WCMD_print_error();
2890 errorlevel = 2;
2891 } else {
2892 WCMD_output_asis(command);
2893 WCMD_output_asis(equalW);
2894 WCMD_output_asis(newValue);
2895 WCMD_output_asis(newline);
2901 /* Clean up */
2902 RegCloseKey(key);
2905 /****************************************************************************
2906 * WCMD_color
2908 * Colors the terminal screen.
2911 void WCMD_color (void) {
2913 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2914 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2916 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2917 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
2918 return;
2921 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2923 COORD topLeft;
2924 DWORD screenSize;
2925 DWORD color = 0;
2927 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2929 topLeft.X = 0;
2930 topLeft.Y = 0;
2932 /* Convert the color hex digits */
2933 if (param1[0] == 0x00) {
2934 color = defaultColor;
2935 } else {
2936 color = strtoulW(param1, NULL, 16);
2939 /* Fail if fg == bg color */
2940 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2941 errorlevel = 1;
2942 return;
2945 /* Set the current screen contents and ensure all future writes
2946 remain this color */
2947 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2948 SetConsoleTextAttribute(hStdOut, color);