advapi32/tests: Do not assume the test is called with an absolute path.
[wine.git] / programs / cmd / builtins.c
blob59fbc8e79a82c2da97134a64296f8326681f9565
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 const WCHAR dotW[] = {'.','\0'};
62 const WCHAR dotdotW[] = {'.','.','\0'};
63 const WCHAR nullW[] = {'\0'};
64 const WCHAR starW[] = {'*','\0'};
65 const WCHAR slashW[] = {'\\','\0'};
66 const WCHAR equalW[] = {'=','\0'};
67 static const WCHAR fslashW[] = {'/','\0'};
68 static const WCHAR onW[] = {'O','N','\0'};
69 static const WCHAR offW[] = {'O','F','F','\0'};
70 static const WCHAR parmY[] = {'/','Y','\0'};
71 static const WCHAR parmNoY[] = {'/','-','Y','\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;
858 * WCMD_strtrim
860 * Returns a trimmed version of s with all leading and trailing whitespace removed
861 * Pre: s non NULL
864 static WCHAR *WCMD_strtrim(const WCHAR *s)
866 DWORD len = strlenW(s);
867 const WCHAR *start = s;
868 WCHAR* result;
870 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
871 return NULL;
873 while (isspaceW(*start)) start++;
874 if (*start) {
875 const WCHAR *end = s + len - 1;
876 while (end > start && isspaceW(*end)) end--;
877 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
878 result[end - start + 1] = '\0';
879 } else {
880 result[0] = '\0';
883 return result;
886 /****************************************************************************
887 * WCMD_echo
889 * Echo input to the screen (or not). We don't try to emulate the bugs
890 * in DOS (try typing "ECHO ON AGAIN" for an example).
893 void WCMD_echo (const WCHAR *command)
895 int count;
896 const WCHAR *origcommand = command;
897 WCHAR *trimmed;
899 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
900 || command[0]==':' || command[0]==';')
901 command++;
903 trimmed = WCMD_strtrim(command);
904 if (!trimmed) return;
906 count = strlenW(trimmed);
907 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
908 && origcommand[0]!=';') {
909 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
910 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
911 return;
914 if (lstrcmpiW(trimmed, onW) == 0)
915 echo_mode = TRUE;
916 else if (lstrcmpiW(trimmed, offW) == 0)
917 echo_mode = FALSE;
918 else {
919 WCMD_output_asis (command);
920 WCMD_output (newline);
922 HeapFree(GetProcessHeap(), 0, trimmed);
925 /**************************************************************************
926 * WCMD_for
928 * Batch file loop processing.
930 * On entry: cmdList contains the syntax up to the set
931 * next cmdList and all in that bracket contain the set data
932 * next cmdlist contains the DO cmd
933 * following that is either brackets or && entries (as per if)
937 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
939 WIN32_FIND_DATAW fd;
940 HANDLE hff;
941 int i;
942 static const WCHAR inW[] = {'i','n'};
943 static const WCHAR doW[] = {'d','o'};
944 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
945 WCHAR variable[4];
946 WCHAR *firstCmd;
947 int thisDepth;
949 WCHAR *curPos = p;
950 BOOL expandDirs = FALSE;
951 BOOL useNumbers = FALSE;
952 BOOL doFileset = FALSE;
953 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
954 int itemNum;
955 CMD_LIST *thisCmdStart;
958 /* Handle optional qualifiers (multiple are allowed) */
959 while (*curPos && *curPos == '/') {
960 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
961 curPos++;
962 switch (toupperW(*curPos)) {
963 case 'D': curPos++; expandDirs = TRUE; break;
964 case 'L': curPos++; useNumbers = TRUE; break;
966 /* Recursive is special case - /R can have an optional path following it */
967 /* filenamesets are another special case - /F can have an optional options following it */
968 case 'R':
969 case 'F':
971 BOOL isRecursive = (*curPos == 'R');
973 if (!isRecursive)
974 doFileset = TRUE;
976 /* Skip whitespace */
977 curPos++;
978 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
980 /* Next parm is either qualifier, path/options or variable -
981 only care about it if it is the path/options */
982 if (*curPos && *curPos != '/' && *curPos != '%') {
983 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
984 else {
985 static unsigned int once;
986 if (!once++) WINE_FIXME("/F needs to handle options\n");
989 break;
991 default:
992 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
993 curPos++;
996 /* Skip whitespace between qualifiers */
997 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1000 /* Skip whitespace before variable */
1001 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1003 /* Ensure line continues with variable */
1004 if (!*curPos || *curPos != '%') {
1005 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1006 return;
1009 /* Variable should follow */
1010 i = 0;
1011 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
1012 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
1013 variable[i] = 0x00;
1014 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1015 curPos = &curPos[i];
1017 /* Skip whitespace before IN */
1018 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1020 /* Ensure line continues with IN */
1021 if (!*curPos
1022 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
1024 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1025 return;
1028 /* Save away where the set of data starts and the variable */
1029 thisDepth = (*cmdList)->bracketDepth;
1030 *cmdList = (*cmdList)->nextcommand;
1031 setStart = (*cmdList);
1033 /* Skip until the close bracket */
1034 WINE_TRACE("Searching %p as the set\n", *cmdList);
1035 while (*cmdList &&
1036 (*cmdList)->command != NULL &&
1037 (*cmdList)->bracketDepth > thisDepth) {
1038 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1039 *cmdList = (*cmdList)->nextcommand;
1042 /* Skip the close bracket, if there is one */
1043 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1045 /* Syntax error if missing close bracket, or nothing following it
1046 and once we have the complete set, we expect a DO */
1047 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1048 if ((*cmdList == NULL)
1049 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1051 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1052 return;
1055 /* Save away the starting position for the commands (and offset for the
1056 first one */
1057 cmdStart = *cmdList;
1058 cmdEnd = *cmdList;
1059 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1060 itemNum = 0;
1062 thisSet = setStart;
1063 /* Loop through all set entries */
1064 while (thisSet &&
1065 thisSet->command != NULL &&
1066 thisSet->bracketDepth >= thisDepth) {
1068 /* Loop through all entries on the same line */
1069 WCHAR *item;
1070 WCHAR *itemStart;
1072 WINE_TRACE("Processing for set %p\n", thisSet);
1073 i = 0;
1074 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1077 * If the parameter within the set has a wildcard then search for matching files
1078 * otherwise do a literal substitution.
1080 static const WCHAR wildcards[] = {'*','?','\0'};
1081 thisCmdStart = cmdStart;
1083 itemNum++;
1084 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1086 if (!useNumbers && !doFileset) {
1087 if (strpbrkW (item, wildcards)) {
1088 hff = FindFirstFileW(item, &fd);
1089 if (hff != INVALID_HANDLE_VALUE) {
1090 do {
1091 BOOL isDirectory = FALSE;
1093 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1095 /* Handle as files or dirs appropriately, but ignore . and .. */
1096 if (isDirectory == expandDirs &&
1097 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1098 (strcmpW(fd.cFileName, dotW) != 0))
1100 thisCmdStart = cmdStart;
1101 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1102 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1103 fd.cFileName, FALSE, TRUE);
1106 } while (FindNextFileW(hff, &fd) != 0);
1107 FindClose (hff);
1109 } else {
1110 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1113 } else if (useNumbers) {
1114 /* Convert the first 3 numbers to signed longs and save */
1115 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1116 /* else ignore them! */
1118 /* Filesets - either a list of files, or a command to run and parse the output */
1119 } else if (doFileset && *itemStart != '"') {
1121 HANDLE input;
1122 WCHAR temp_file[MAX_PATH];
1124 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1125 wine_dbgstr_w(item));
1127 /* If backquote or single quote, we need to launch that command
1128 and parse the results - use a temporary file */
1129 if (*itemStart == '`' || *itemStart == '\'') {
1131 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1132 static const WCHAR redirOut[] = {'>','%','s','\0'};
1133 static const WCHAR cmdW[] = {'C','M','D','\0'};
1135 /* Remove trailing character */
1136 itemStart[strlenW(itemStart)-1] = 0x00;
1138 /* Get temp filename */
1139 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1140 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1142 /* Execute program and redirect output */
1143 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1144 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1146 /* Open the file, read line by line and process */
1147 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1148 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1149 } else {
1151 /* Open the file, read line by line and process */
1152 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1153 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1156 /* Process the input file */
1157 if (input == INVALID_HANDLE_VALUE) {
1158 WCMD_print_error ();
1159 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1160 errorlevel = 1;
1161 return; /* FOR loop aborts at first failure here */
1163 } else {
1165 WCHAR buffer[MAXSTRING] = {'\0'};
1166 WCHAR *where, *parm;
1168 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1170 /* Skip blank lines*/
1171 parm = WCMD_parameter (buffer, 0, &where, NULL);
1172 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1173 wine_dbgstr_w(buffer));
1175 if (where) {
1176 /* FIXME: The following should be moved into its own routine and
1177 reused for the string literal parsing below */
1178 thisCmdStart = cmdStart;
1179 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1180 cmdEnd = thisCmdStart;
1183 buffer[0] = 0x00;
1186 CloseHandle (input);
1189 /* Delete the temporary file */
1190 if (*itemStart == '`' || *itemStart == '\'') {
1191 DeleteFileW(temp_file);
1194 /* Filesets - A string literal */
1195 } else if (doFileset && *itemStart == '"') {
1196 WCHAR buffer[MAXSTRING] = {'\0'};
1197 WCHAR *where, *parm;
1199 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1200 strcpyW(buffer, item);
1201 parm = WCMD_parameter (buffer, 0, &where, NULL);
1202 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1203 wine_dbgstr_w(buffer));
1205 if (where) {
1206 /* FIXME: The following should be moved into its own routine and
1207 reused for the string literal parsing below */
1208 thisCmdStart = cmdStart;
1209 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1210 cmdEnd = thisCmdStart;
1214 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1215 cmdEnd = thisCmdStart;
1216 i++;
1219 /* Move onto the next set line */
1220 thisSet = thisSet->nextcommand;
1223 /* If /L is provided, now run the for loop */
1224 if (useNumbers) {
1225 WCHAR thisNum[20];
1226 static const WCHAR fmt[] = {'%','d','\0'};
1228 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1229 numbers[0], numbers[2], numbers[1]);
1230 for (i=numbers[0];
1231 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1232 i=i + numbers[1]) {
1234 sprintfW(thisNum, fmt, i);
1235 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1237 thisCmdStart = cmdStart;
1238 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1239 cmdEnd = thisCmdStart;
1243 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1244 all processing, OR it should be pointing to the end of && processing OR
1245 it should be pointing at the NULL end of bracket for the DO. The return
1246 value needs to be the NEXT command to execute, which it either is, or
1247 we need to step over the closing bracket */
1248 *cmdList = cmdEnd;
1249 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1253 /*****************************************************************************
1254 * WCMD_part_execute
1256 * Execute a command, and any && or bracketed follow on to the command. The
1257 * first command to be executed may not be at the front of the
1258 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1260 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1261 const WCHAR *variable, const WCHAR *value,
1262 BOOL isIF, BOOL conditionTRUE) {
1264 CMD_LIST *curPosition = *cmdList;
1265 int myDepth = (*cmdList)->bracketDepth;
1267 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1268 cmdList, wine_dbgstr_w(firstcmd),
1269 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1270 conditionTRUE);
1272 /* Skip leading whitespace between condition and the command */
1273 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1275 /* Process the first command, if there is one */
1276 if (conditionTRUE && firstcmd && *firstcmd) {
1277 WCHAR *command = WCMD_strdupW(firstcmd);
1278 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1279 HeapFree(GetProcessHeap(), 0, command);
1283 /* If it didn't move the position, step to next command */
1284 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1286 /* Process any other parts of the command */
1287 if (*cmdList) {
1288 BOOL processThese = TRUE;
1290 if (isIF) processThese = conditionTRUE;
1292 while (*cmdList) {
1293 static const WCHAR ifElse[] = {'e','l','s','e'};
1295 /* execute all appropriate commands */
1296 curPosition = *cmdList;
1298 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1299 *cmdList,
1300 (*cmdList)->prevDelim,
1301 (*cmdList)->bracketDepth, myDepth);
1303 /* Execute any statements appended to the line */
1304 /* FIXME: Only if previous call worked for && or failed for || */
1305 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1306 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1307 if (processThese && (*cmdList)->command) {
1308 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1309 value, cmdList);
1311 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1313 /* Execute any appended to the statement with (...) */
1314 } else if ((*cmdList)->bracketDepth > myDepth) {
1315 if (processThese) {
1316 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1317 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1319 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1321 /* End of the command - does 'ELSE ' follow as the next command? */
1322 } else {
1323 if (isIF
1324 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1325 (*cmdList)->command)) {
1327 /* Swap between if and else processing */
1328 processThese = !processThese;
1330 /* Process the ELSE part */
1331 if (processThese) {
1332 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1333 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1335 /* Skip leading whitespace between condition and the command */
1336 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1337 if (*cmd) {
1338 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1341 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1342 } else {
1343 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1344 break;
1349 return;
1352 /**************************************************************************
1353 * WCMD_give_help
1355 * Simple on-line help. Help text is stored in the resource file.
1358 void WCMD_give_help (const WCHAR *command)
1360 size_t i;
1362 command = WCMD_skip_leading_spaces((WCHAR*) command);
1363 if (strlenW(command) == 0) {
1364 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1366 else {
1367 /* Display help message for builtin commands */
1368 for (i=0; i<=WCMD_EXIT; i++) {
1369 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1370 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1371 WCMD_output_asis (WCMD_LoadMessage(i));
1372 return;
1375 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1376 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1377 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1378 command, -1, externals[i], -1) == CSTR_EQUAL) {
1379 WCHAR cmd[128];
1380 static const WCHAR helpW[] = {' ', '/','?','\0'};
1381 strcpyW(cmd, command);
1382 strcatW(cmd, helpW);
1383 WCMD_run_program(cmd, 0);
1384 return;
1387 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1389 return;
1392 /****************************************************************************
1393 * WCMD_go_to
1395 * Batch file jump instruction. Not the most efficient algorithm ;-)
1396 * Prints error message if the specified label cannot be found - the file pointer is
1397 * then at EOF, effectively stopping the batch file.
1398 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1401 void WCMD_goto (CMD_LIST **cmdList) {
1403 WCHAR string[MAX_PATH];
1404 WCHAR current[MAX_PATH];
1406 /* Do not process any more parts of a processed multipart or multilines command */
1407 if (cmdList) *cmdList = NULL;
1409 if (context != NULL) {
1410 WCHAR *paramStart = param1, *str;
1411 static const WCHAR eofW[] = {':','e','o','f','\0'};
1413 if (param1[0] == 0x00) {
1414 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1415 return;
1418 /* Handle special :EOF label */
1419 if (lstrcmpiW (eofW, param1) == 0) {
1420 context -> skip_rest = TRUE;
1421 return;
1424 /* Support goto :label as well as goto label */
1425 if (*paramStart == ':') paramStart++;
1427 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1428 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1429 str = string;
1430 while (isspaceW (*str)) str++;
1431 if (*str == ':') {
1432 DWORD index = 0;
1433 str++;
1434 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1435 index++;
1437 /* ignore space at the end */
1438 current[index] = 0;
1439 if (lstrcmpiW (current, paramStart) == 0) return;
1442 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1444 return;
1447 /*****************************************************************************
1448 * WCMD_pushd
1450 * Push a directory onto the stack
1453 void WCMD_pushd (const WCHAR *command)
1455 struct env_stack *curdir;
1456 WCHAR *thisdir;
1457 static const WCHAR parmD[] = {'/','D','\0'};
1459 if (strchrW(command, '/') != NULL) {
1460 SetLastError(ERROR_INVALID_PARAMETER);
1461 WCMD_print_error();
1462 return;
1465 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1466 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1467 if( !curdir || !thisdir ) {
1468 LocalFree(curdir);
1469 LocalFree(thisdir);
1470 WINE_ERR ("out of memory\n");
1471 return;
1474 /* Change directory using CD code with /D parameter */
1475 strcpyW(quals, parmD);
1476 GetCurrentDirectoryW (1024, thisdir);
1477 errorlevel = 0;
1478 WCMD_setshow_default(command);
1479 if (errorlevel) {
1480 LocalFree(curdir);
1481 LocalFree(thisdir);
1482 return;
1483 } else {
1484 curdir -> next = pushd_directories;
1485 curdir -> strings = thisdir;
1486 if (pushd_directories == NULL) {
1487 curdir -> u.stackdepth = 1;
1488 } else {
1489 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1491 pushd_directories = curdir;
1496 /*****************************************************************************
1497 * WCMD_popd
1499 * Pop a directory from the stack
1502 void WCMD_popd (void) {
1503 struct env_stack *temp = pushd_directories;
1505 if (!pushd_directories)
1506 return;
1508 /* pop the old environment from the stack, and make it the current dir */
1509 pushd_directories = temp->next;
1510 SetCurrentDirectoryW(temp->strings);
1511 LocalFree (temp->strings);
1512 LocalFree (temp);
1515 /****************************************************************************
1516 * WCMD_if
1518 * Batch file conditional.
1520 * On entry, cmdlist will point to command containing the IF, and optionally
1521 * the first command to execute (if brackets not found)
1522 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1523 * If ('s were found, execute all within that bracket
1524 * Command may optionally be followed by an ELSE - need to skip instructions
1525 * in the else using the same logic
1527 * FIXME: Much more syntax checking needed!
1530 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1532 int negate; /* Negate condition */
1533 int test; /* Condition evaluation result */
1534 WCHAR condition[MAX_PATH], *command, *s;
1535 static const WCHAR notW[] = {'n','o','t','\0'};
1536 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1537 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1538 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1539 static const WCHAR eqeqW[] = {'=','=','\0'};
1540 static const WCHAR parmI[] = {'/','I','\0'};
1541 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1543 negate = !lstrcmpiW(param1,notW);
1544 strcpyW(condition, (negate ? param2 : param1));
1545 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1547 if (!lstrcmpiW (condition, errlvlW)) {
1548 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL);
1549 WCHAR *endptr;
1550 long int param_int = strtolW(param, &endptr, 10);
1551 if (*endptr) {
1552 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1553 return;
1555 test = ((long int)errorlevel >= param_int);
1556 WCMD_parameter(p, 2+negate, &command, NULL);
1558 else if (!lstrcmpiW (condition, existW)) {
1559 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1560 WCMD_parameter(p, 2+negate, &command, NULL);
1562 else if (!lstrcmpiW (condition, defdW)) {
1563 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1564 WCMD_parameter(p, 2+negate, &command, NULL);
1566 else if ((s = strstrW (p, eqeqW))) {
1567 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1568 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1569 s += 2;
1570 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1571 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1572 test = caseInsensitive
1573 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1574 leftPart, leftPartEnd-leftPart+1,
1575 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1576 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1577 leftPart, leftPartEnd-leftPart+1,
1578 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1579 WCMD_parameter(s, 1, &command, NULL);
1581 else {
1582 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1583 return;
1586 /* Process rest of IF statement which is on the same line
1587 Note: This may process all or some of the cmdList (eg a GOTO) */
1588 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1591 /****************************************************************************
1592 * WCMD_move
1594 * Move a file, directory tree or wildcarded set of files.
1597 void WCMD_move (void)
1599 int status;
1600 WIN32_FIND_DATAW fd;
1601 HANDLE hff;
1602 WCHAR input[MAX_PATH];
1603 WCHAR output[MAX_PATH];
1604 WCHAR drive[10];
1605 WCHAR dir[MAX_PATH];
1606 WCHAR fname[MAX_PATH];
1607 WCHAR ext[MAX_PATH];
1609 if (param1[0] == 0x00) {
1610 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1611 return;
1614 /* If no destination supplied, assume current directory */
1615 if (param2[0] == 0x00) {
1616 strcpyW(param2, dotW);
1619 /* If 2nd parm is directory, then use original filename */
1620 /* Convert partial path to full path */
1621 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1622 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1623 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1624 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1626 /* Split into components */
1627 WCMD_splitpath(input, drive, dir, fname, ext);
1629 hff = FindFirstFileW(input, &fd);
1630 if (hff == INVALID_HANDLE_VALUE)
1631 return;
1633 do {
1634 WCHAR dest[MAX_PATH];
1635 WCHAR src[MAX_PATH];
1636 DWORD attribs;
1637 BOOL ok = TRUE;
1639 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1641 /* Build src & dest name */
1642 strcpyW(src, drive);
1643 strcatW(src, dir);
1645 /* See if dest is an existing directory */
1646 attribs = GetFileAttributesW(output);
1647 if (attribs != INVALID_FILE_ATTRIBUTES &&
1648 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1649 strcpyW(dest, output);
1650 strcatW(dest, slashW);
1651 strcatW(dest, fd.cFileName);
1652 } else {
1653 strcpyW(dest, output);
1656 strcatW(src, fd.cFileName);
1658 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1659 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1661 /* If destination exists, prompt unless /Y supplied */
1662 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1663 BOOL force = FALSE;
1664 WCHAR copycmd[MAXSTRING];
1665 DWORD len;
1667 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1668 if (strstrW (quals, parmNoY))
1669 force = FALSE;
1670 else if (strstrW (quals, parmY))
1671 force = TRUE;
1672 else {
1673 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1674 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1675 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1676 && ! lstrcmpiW (copycmd, parmY));
1679 /* Prompt if overwriting */
1680 if (!force) {
1681 WCHAR question[MAXSTRING];
1682 WCHAR yesChar[10];
1684 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1686 /* Ask for confirmation */
1687 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1688 ok = WCMD_ask_confirm(question, FALSE, NULL);
1690 /* So delete the destination prior to the move */
1691 if (ok) {
1692 if (!DeleteFileW(dest)) {
1693 WCMD_print_error ();
1694 errorlevel = 1;
1695 ok = FALSE;
1701 if (ok) {
1702 status = MoveFileW(src, dest);
1703 } else {
1704 status = 1; /* Anything other than 0 to prevent error msg below */
1707 if (!status) {
1708 WCMD_print_error ();
1709 errorlevel = 1;
1711 } while (FindNextFileW(hff, &fd) != 0);
1713 FindClose(hff);
1716 /****************************************************************************
1717 * WCMD_pause
1719 * Suspend execution of a batch script until a key is typed
1722 void WCMD_pause (void)
1724 DWORD oldmode;
1725 BOOL have_console;
1726 DWORD count;
1727 WCHAR key;
1728 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1730 have_console = GetConsoleMode(hIn, &oldmode);
1731 if (have_console)
1732 SetConsoleMode(hIn, 0);
1734 WCMD_output(anykey);
1735 WCMD_ReadFile(hIn, &key, 1, &count);
1736 if (have_console)
1737 SetConsoleMode(hIn, oldmode);
1740 /****************************************************************************
1741 * WCMD_remove_dir
1743 * Delete a directory.
1746 void WCMD_remove_dir (WCHAR *command) {
1748 int argno = 0;
1749 int argsProcessed = 0;
1750 WCHAR *argN = command;
1751 static const WCHAR parmS[] = {'/','S','\0'};
1752 static const WCHAR parmQ[] = {'/','Q','\0'};
1754 /* Loop through all args */
1755 while (argN) {
1756 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1757 if (argN && argN[0] != '/') {
1758 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1759 wine_dbgstr_w(quals));
1760 argsProcessed++;
1762 /* If subdirectory search not supplied, just try to remove
1763 and report error if it fails (eg if it contains a file) */
1764 if (strstrW (quals, parmS) == NULL) {
1765 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1767 /* Otherwise use ShFileOp to recursively remove a directory */
1768 } else {
1770 SHFILEOPSTRUCTW lpDir;
1772 /* Ask first */
1773 if (strstrW (quals, parmQ) == NULL) {
1774 BOOL ok;
1775 WCHAR question[MAXSTRING];
1776 static const WCHAR fmt[] = {'%','s',' ','\0'};
1778 /* Ask for confirmation */
1779 wsprintfW(question, fmt, thisArg);
1780 ok = WCMD_ask_confirm(question, TRUE, NULL);
1782 /* Abort if answer is 'N' */
1783 if (!ok) return;
1786 /* Do the delete */
1787 lpDir.hwnd = NULL;
1788 lpDir.pTo = NULL;
1789 lpDir.pFrom = thisArg;
1790 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1791 lpDir.wFunc = FO_DELETE;
1792 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1797 /* Handle no valid args */
1798 if (argsProcessed == 0) {
1799 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1800 return;
1805 /****************************************************************************
1806 * WCMD_rename
1808 * Rename a file.
1811 void WCMD_rename (void)
1813 int status;
1814 HANDLE hff;
1815 WIN32_FIND_DATAW fd;
1816 WCHAR input[MAX_PATH];
1817 WCHAR *dotDst = NULL;
1818 WCHAR drive[10];
1819 WCHAR dir[MAX_PATH];
1820 WCHAR fname[MAX_PATH];
1821 WCHAR ext[MAX_PATH];
1823 errorlevel = 0;
1825 /* Must be at least two args */
1826 if (param1[0] == 0x00 || param2[0] == 0x00) {
1827 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1828 errorlevel = 1;
1829 return;
1832 /* Destination cannot contain a drive letter or directory separator */
1833 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1834 SetLastError(ERROR_INVALID_PARAMETER);
1835 WCMD_print_error();
1836 errorlevel = 1;
1837 return;
1840 /* Convert partial path to full path */
1841 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1842 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1843 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1844 dotDst = strchrW(param2, '.');
1846 /* Split into components */
1847 WCMD_splitpath(input, drive, dir, fname, ext);
1849 hff = FindFirstFileW(input, &fd);
1850 if (hff == INVALID_HANDLE_VALUE)
1851 return;
1853 do {
1854 WCHAR dest[MAX_PATH];
1855 WCHAR src[MAX_PATH];
1856 WCHAR *dotSrc = NULL;
1857 int dirLen;
1859 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1861 /* FIXME: If dest name or extension is *, replace with filename/ext
1862 part otherwise use supplied name. This supports:
1863 ren *.fred *.jim
1864 ren jim.* fred.* etc
1865 However, windows has a more complex algorithm supporting eg
1866 ?'s and *'s mid name */
1867 dotSrc = strchrW(fd.cFileName, '.');
1869 /* Build src & dest name */
1870 strcpyW(src, drive);
1871 strcatW(src, dir);
1872 strcpyW(dest, src);
1873 dirLen = strlenW(src);
1874 strcatW(src, fd.cFileName);
1876 /* Build name */
1877 if (param2[0] == '*') {
1878 strcatW(dest, fd.cFileName);
1879 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1880 } else {
1881 strcatW(dest, param2);
1882 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1885 /* Build Extension */
1886 if (dotDst && (*(dotDst+1)=='*')) {
1887 if (dotSrc) strcatW(dest, dotSrc);
1888 } else if (dotDst) {
1889 if (dotDst) strcatW(dest, dotDst);
1892 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1893 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1895 status = MoveFileW(src, dest);
1897 if (!status) {
1898 WCMD_print_error ();
1899 errorlevel = 1;
1901 } while (FindNextFileW(hff, &fd) != 0);
1903 FindClose(hff);
1906 /*****************************************************************************
1907 * WCMD_dupenv
1909 * Make a copy of the environment.
1911 static WCHAR *WCMD_dupenv( const WCHAR *env )
1913 WCHAR *env_copy;
1914 int len;
1916 if( !env )
1917 return NULL;
1919 len = 0;
1920 while ( env[len] )
1921 len += (strlenW(&env[len]) + 1);
1923 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1924 if (!env_copy)
1926 WINE_ERR("out of memory\n");
1927 return env_copy;
1929 memcpy (env_copy, env, len*sizeof (WCHAR));
1930 env_copy[len] = 0;
1932 return env_copy;
1935 /*****************************************************************************
1936 * WCMD_setlocal
1938 * setlocal pushes the environment onto a stack
1939 * Save the environment as unicode so we don't screw anything up.
1941 void WCMD_setlocal (const WCHAR *s) {
1942 WCHAR *env;
1943 struct env_stack *env_copy;
1944 WCHAR cwd[MAX_PATH];
1946 /* DISABLEEXTENSIONS ignored */
1948 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1949 if( !env_copy )
1951 WINE_ERR ("out of memory\n");
1952 return;
1955 env = GetEnvironmentStringsW ();
1957 env_copy->strings = WCMD_dupenv (env);
1958 if (env_copy->strings)
1960 env_copy->next = saved_environment;
1961 saved_environment = env_copy;
1963 /* Save the current drive letter */
1964 GetCurrentDirectoryW(MAX_PATH, cwd);
1965 env_copy->u.cwd = cwd[0];
1967 else
1968 LocalFree (env_copy);
1970 FreeEnvironmentStringsW (env);
1974 /*****************************************************************************
1975 * WCMD_endlocal
1977 * endlocal pops the environment off a stack
1978 * Note: When searching for '=', search from WCHAR position 1, to handle
1979 * special internal environment variables =C:, =D: etc
1981 void WCMD_endlocal (void) {
1982 WCHAR *env, *old, *p;
1983 struct env_stack *temp;
1984 int len, n;
1986 if (!saved_environment)
1987 return;
1989 /* pop the old environment from the stack */
1990 temp = saved_environment;
1991 saved_environment = temp->next;
1993 /* delete the current environment, totally */
1994 env = GetEnvironmentStringsW ();
1995 old = WCMD_dupenv (GetEnvironmentStringsW ());
1996 len = 0;
1997 while (old[len]) {
1998 n = strlenW(&old[len]) + 1;
1999 p = strchrW(&old[len] + 1, '=');
2000 if (p)
2002 *p++ = 0;
2003 SetEnvironmentVariableW (&old[len], NULL);
2005 len += n;
2007 LocalFree (old);
2008 FreeEnvironmentStringsW (env);
2010 /* restore old environment */
2011 env = temp->strings;
2012 len = 0;
2013 while (env[len]) {
2014 n = strlenW(&env[len]) + 1;
2015 p = strchrW(&env[len] + 1, '=');
2016 if (p)
2018 *p++ = 0;
2019 SetEnvironmentVariableW (&env[len], p);
2021 len += n;
2024 /* Restore current drive letter */
2025 if (IsCharAlphaW(temp->u.cwd)) {
2026 WCHAR envvar[4];
2027 WCHAR cwd[MAX_PATH];
2028 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2030 wsprintfW(envvar, fmt, temp->u.cwd);
2031 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2032 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2033 SetCurrentDirectoryW(cwd);
2037 LocalFree (env);
2038 LocalFree (temp);
2041 /*****************************************************************************
2042 * WCMD_setshow_default
2044 * Set/Show the current default directory
2047 void WCMD_setshow_default (const WCHAR *command) {
2049 BOOL status;
2050 WCHAR string[1024];
2051 WCHAR cwd[1024];
2052 WCHAR *pos;
2053 WIN32_FIND_DATAW fd;
2054 HANDLE hff;
2055 static const WCHAR parmD[] = {'/','D','\0'};
2057 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2059 /* Skip /D and trailing whitespace if on the front of the command line */
2060 if (CompareStringW(LOCALE_USER_DEFAULT,
2061 NORM_IGNORECASE | SORT_STRINGSORT,
2062 command, 2, parmD, -1) == CSTR_EQUAL) {
2063 command += 2;
2064 while (*command && (*command==' ' || *command=='\t'))
2065 command++;
2068 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2069 if (strlenW(command) == 0) {
2070 strcatW (cwd, newline);
2071 WCMD_output (cwd);
2073 else {
2074 /* Remove any double quotes, which may be in the
2075 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2076 pos = string;
2077 while (*command) {
2078 if (*command != '"') *pos++ = *command;
2079 command++;
2081 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2082 pos--;
2083 *pos = 0x00;
2085 /* Search for appropriate directory */
2086 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2087 hff = FindFirstFileW(string, &fd);
2088 if (hff != INVALID_HANDLE_VALUE) {
2089 do {
2090 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2091 WCHAR fpath[MAX_PATH];
2092 WCHAR drive[10];
2093 WCHAR dir[MAX_PATH];
2094 WCHAR fname[MAX_PATH];
2095 WCHAR ext[MAX_PATH];
2096 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2098 /* Convert path into actual directory spec */
2099 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2100 WCMD_splitpath(fpath, drive, dir, fname, ext);
2102 /* Rebuild path */
2103 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2104 break;
2106 } while (FindNextFileW(hff, &fd) != 0);
2107 FindClose(hff);
2110 /* Change to that directory */
2111 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2113 status = SetCurrentDirectoryW(string);
2114 if (!status) {
2115 errorlevel = 1;
2116 WCMD_print_error ();
2117 return;
2118 } else {
2120 /* Save away the actual new directory, to store as current location */
2121 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2123 /* Restore old directory if drive letter would change, and
2124 CD x:\directory /D (or pushd c:\directory) not supplied */
2125 if ((strstrW(quals, parmD) == NULL) &&
2126 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2127 SetCurrentDirectoryW(cwd);
2131 /* Set special =C: type environment variable, for drive letter of
2132 change of directory, even if path was restored due to missing
2133 /D (allows changing drive letter when not resident on that
2134 drive */
2135 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2136 WCHAR env[4];
2137 strcpyW(env, equalW);
2138 memcpy(env+1, string, 2 * sizeof(WCHAR));
2139 env[3] = 0x00;
2140 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2141 SetEnvironmentVariableW(env, string);
2145 return;
2148 /****************************************************************************
2149 * WCMD_setshow_date
2151 * Set/Show the system date
2152 * FIXME: Can't change date yet
2155 void WCMD_setshow_date (void) {
2157 WCHAR curdate[64], buffer[64];
2158 DWORD count;
2159 static const WCHAR parmT[] = {'/','T','\0'};
2161 if (strlenW(param1) == 0) {
2162 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2163 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2164 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2165 if (strstrW (quals, parmT) == NULL) {
2166 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2167 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2168 if (count > 2) {
2169 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2173 else WCMD_print_error ();
2175 else {
2176 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2180 /****************************************************************************
2181 * WCMD_compare
2183 static int WCMD_compare( const void *a, const void *b )
2185 int r;
2186 const WCHAR * const *str_a = a, * const *str_b = b;
2187 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2188 *str_a, -1, *str_b, -1 );
2189 if( r == CSTR_LESS_THAN ) return -1;
2190 if( r == CSTR_GREATER_THAN ) return 1;
2191 return 0;
2194 /****************************************************************************
2195 * WCMD_setshow_sortenv
2197 * sort variables into order for display
2198 * Optionally only display those who start with a stub
2199 * returns the count displayed
2201 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2203 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2204 const WCHAR **str;
2206 if (stub) stublen = strlenW(stub);
2208 /* count the number of strings, and the total length */
2209 while ( s[len] ) {
2210 len += (strlenW(&s[len]) + 1);
2211 count++;
2214 /* add the strings to an array */
2215 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2216 if( !str )
2217 return 0;
2218 str[0] = s;
2219 for( i=1; i<count; i++ )
2220 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2222 /* sort the array */
2223 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2225 /* print it */
2226 for( i=0; i<count; i++ ) {
2227 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2228 NORM_IGNORECASE | SORT_STRINGSORT,
2229 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2230 /* Don't display special internal variables */
2231 if (str[i][0] != '=') {
2232 WCMD_output_asis(str[i]);
2233 WCMD_output_asis(newline);
2234 displayedcount++;
2239 LocalFree( str );
2240 return displayedcount;
2243 /****************************************************************************
2244 * WCMD_setshow_env
2246 * Set/Show the environment variables
2249 void WCMD_setshow_env (WCHAR *s) {
2251 LPVOID env;
2252 WCHAR *p;
2253 int status;
2254 static const WCHAR parmP[] = {'/','P','\0'};
2256 if (param1[0] == 0x00 && quals[0] == 0x00) {
2257 env = GetEnvironmentStringsW();
2258 WCMD_setshow_sortenv( env, NULL );
2259 return;
2262 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2263 if (CompareStringW(LOCALE_USER_DEFAULT,
2264 NORM_IGNORECASE | SORT_STRINGSORT,
2265 s, 2, parmP, -1) == CSTR_EQUAL) {
2266 WCHAR string[MAXSTRING];
2267 DWORD count;
2269 s += 2;
2270 while (*s && (*s==' ' || *s=='\t')) s++;
2271 if (*s=='\"')
2272 WCMD_strip_quotes(s);
2274 /* If no parameter, or no '=' sign, return an error */
2275 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2276 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2277 return;
2280 /* Output the prompt */
2281 *p++ = '\0';
2282 if (strlenW(p) != 0) WCMD_output(p);
2284 /* Read the reply */
2285 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2286 if (count > 1) {
2287 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2288 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2289 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2290 wine_dbgstr_w(string));
2291 status = SetEnvironmentVariableW(s, string);
2294 } else {
2295 DWORD gle;
2297 if (*s=='\"')
2298 WCMD_strip_quotes(s);
2299 p = strchrW (s, '=');
2300 if (p == NULL) {
2301 env = GetEnvironmentStringsW();
2302 if (WCMD_setshow_sortenv( env, s ) == 0) {
2303 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2304 errorlevel = 1;
2306 return;
2308 *p++ = '\0';
2310 if (strlenW(p) == 0) p = NULL;
2311 status = SetEnvironmentVariableW(s, p);
2312 gle = GetLastError();
2313 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2314 errorlevel = 1;
2315 } else if ((!status)) WCMD_print_error();
2319 /****************************************************************************
2320 * WCMD_setshow_path
2322 * Set/Show the path environment variable
2325 void WCMD_setshow_path (const WCHAR *command) {
2327 WCHAR string[1024];
2328 DWORD status;
2329 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2330 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2332 if (strlenW(param1) == 0) {
2333 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2334 if (status != 0) {
2335 WCMD_output_asis ( pathEqW);
2336 WCMD_output_asis ( string);
2337 WCMD_output_asis ( newline);
2339 else {
2340 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2343 else {
2344 if (*command == '=') command++; /* Skip leading '=' */
2345 status = SetEnvironmentVariableW(pathW, command);
2346 if (!status) WCMD_print_error();
2350 /****************************************************************************
2351 * WCMD_setshow_prompt
2353 * Set or show the command prompt.
2356 void WCMD_setshow_prompt (void) {
2358 WCHAR *s;
2359 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2361 if (strlenW(param1) == 0) {
2362 SetEnvironmentVariableW(promptW, NULL);
2364 else {
2365 s = param1;
2366 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2367 if (strlenW(s) == 0) {
2368 SetEnvironmentVariableW(promptW, NULL);
2370 else SetEnvironmentVariableW(promptW, s);
2374 /****************************************************************************
2375 * WCMD_setshow_time
2377 * Set/Show the system time
2378 * FIXME: Can't change time yet
2381 void WCMD_setshow_time (void) {
2383 WCHAR curtime[64], buffer[64];
2384 DWORD count;
2385 SYSTEMTIME st;
2386 static const WCHAR parmT[] = {'/','T','\0'};
2388 if (strlenW(param1) == 0) {
2389 GetLocalTime(&st);
2390 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2391 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2392 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2393 if (strstrW (quals, parmT) == NULL) {
2394 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2395 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2396 if (count > 2) {
2397 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2401 else WCMD_print_error ();
2403 else {
2404 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2408 /****************************************************************************
2409 * WCMD_shift
2411 * Shift batch parameters.
2412 * Optional /n says where to start shifting (n=0-8)
2415 void WCMD_shift (const WCHAR *command) {
2416 int start;
2418 if (context != NULL) {
2419 WCHAR *pos = strchrW(command, '/');
2420 int i;
2422 if (pos == NULL) {
2423 start = 0;
2424 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2425 start = (*(pos+1) - '0');
2426 } else {
2427 SetLastError(ERROR_INVALID_PARAMETER);
2428 WCMD_print_error();
2429 return;
2432 WINE_TRACE("Shifting variables, starting at %d\n", start);
2433 for (i=start;i<=8;i++) {
2434 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2436 context -> shift_count[9] = context -> shift_count[9] + 1;
2441 /****************************************************************************
2442 * WCMD_title
2444 * Set the console title
2446 void WCMD_title (const WCHAR *command) {
2447 SetConsoleTitleW(command);
2450 /****************************************************************************
2451 * WCMD_type
2453 * Copy a file to standard output.
2456 void WCMD_type (WCHAR *command) {
2458 int argno = 0;
2459 WCHAR *argN = command;
2460 BOOL writeHeaders = FALSE;
2462 if (param1[0] == 0x00) {
2463 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2464 return;
2467 if (param2[0] != 0x00) writeHeaders = TRUE;
2469 /* Loop through all args */
2470 errorlevel = 0;
2471 while (argN) {
2472 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2474 HANDLE h;
2475 WCHAR buffer[512];
2476 DWORD count;
2478 if (!argN) break;
2480 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2481 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2482 FILE_ATTRIBUTE_NORMAL, NULL);
2483 if (h == INVALID_HANDLE_VALUE) {
2484 WCMD_print_error ();
2485 WCMD_output(WCMD_LoadMessage(WCMD_READFAIL), thisArg); /* should be _stderr */
2486 errorlevel = 1;
2487 } else {
2488 if (writeHeaders) {
2489 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2490 WCMD_output(fmt, thisArg);
2492 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2493 if (count == 0) break; /* ReadFile reports success on EOF! */
2494 buffer[count] = 0;
2495 WCMD_output_asis (buffer);
2497 CloseHandle (h);
2502 /****************************************************************************
2503 * WCMD_more
2505 * Output either a file or stdin to screen in pages
2508 void WCMD_more (WCHAR *command) {
2510 int argno = 0;
2511 WCHAR *argN = command;
2512 WCHAR moreStr[100];
2513 WCHAR moreStrPage[100];
2514 WCHAR buffer[512];
2515 DWORD count;
2516 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2517 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2518 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2519 ')',' ','-','-','\n','\0'};
2520 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2522 /* Prefix the NLS more with '-- ', then load the text */
2523 errorlevel = 0;
2524 strcpyW(moreStr, moreStart);
2525 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2526 (sizeof(moreStr)/sizeof(WCHAR))-3);
2528 if (param1[0] == 0x00) {
2530 /* Wine implements pipes via temporary files, and hence stdin is
2531 effectively reading from the file. This means the prompts for
2532 more are satisfied by the next line from the input (file). To
2533 avoid this, ensure stdin is to the console */
2534 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2535 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2536 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2537 FILE_ATTRIBUTE_NORMAL, 0);
2538 WINE_TRACE("No parms - working probably in pipe mode\n");
2539 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2541 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2542 once you get in this bit unless due to a pipe, its going to end badly... */
2543 wsprintfW(moreStrPage, moreFmt, moreStr);
2545 WCMD_enter_paged_mode(moreStrPage);
2546 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2547 if (count == 0) break; /* ReadFile reports success on EOF! */
2548 buffer[count] = 0;
2549 WCMD_output_asis (buffer);
2551 WCMD_leave_paged_mode();
2553 /* Restore stdin to what it was */
2554 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2555 CloseHandle(hConIn);
2557 return;
2558 } else {
2559 BOOL needsPause = FALSE;
2561 /* Loop through all args */
2562 WINE_TRACE("Parms supplied - working through each file\n");
2563 WCMD_enter_paged_mode(moreStrPage);
2565 while (argN) {
2566 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2567 HANDLE h;
2569 if (!argN) break;
2571 if (needsPause) {
2573 /* Wait */
2574 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2575 WCMD_leave_paged_mode();
2576 WCMD_output_asis(moreStrPage);
2577 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2578 WCMD_enter_paged_mode(moreStrPage);
2582 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2583 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2584 FILE_ATTRIBUTE_NORMAL, NULL);
2585 if (h == INVALID_HANDLE_VALUE) {
2586 WCMD_print_error ();
2587 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2588 errorlevel = 1;
2589 } else {
2590 ULONG64 curPos = 0;
2591 ULONG64 fileLen = 0;
2592 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2594 /* Get the file size */
2595 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2596 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2598 needsPause = TRUE;
2599 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2600 if (count == 0) break; /* ReadFile reports success on EOF! */
2601 buffer[count] = 0;
2602 curPos += count;
2604 /* Update % count (would be used in WCMD_output_asis as prompt) */
2605 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2607 WCMD_output_asis (buffer);
2609 CloseHandle (h);
2613 WCMD_leave_paged_mode();
2617 /****************************************************************************
2618 * WCMD_verify
2620 * Display verify flag.
2621 * FIXME: We don't actually do anything with the verify flag other than toggle
2622 * it...
2625 void WCMD_verify (const WCHAR *command) {
2627 int count;
2629 count = strlenW(command);
2630 if (count == 0) {
2631 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2632 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2633 return;
2635 if (lstrcmpiW(command, onW) == 0) {
2636 verify_mode = TRUE;
2637 return;
2639 else if (lstrcmpiW(command, offW) == 0) {
2640 verify_mode = FALSE;
2641 return;
2643 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2646 /****************************************************************************
2647 * WCMD_version
2649 * Display version info.
2652 void WCMD_version (void) {
2654 WCMD_output (version_string);
2658 /****************************************************************************
2659 * WCMD_volume
2661 * Display volume information (set_label = FALSE)
2662 * Additionally set volume label (set_label = TRUE)
2663 * Returns 1 on success, 0 otherwise
2666 int WCMD_volume(BOOL set_label, const WCHAR *path)
2668 DWORD count, serial;
2669 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2670 BOOL status;
2672 if (strlenW(path) == 0) {
2673 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2674 if (!status) {
2675 WCMD_print_error ();
2676 return 0;
2678 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2679 &serial, NULL, NULL, NULL, 0);
2681 else {
2682 static const WCHAR fmt[] = {'%','s','\\','\0'};
2683 if ((path[1] != ':') || (strlenW(path) != 2)) {
2684 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2685 return 0;
2687 wsprintfW (curdir, fmt, path);
2688 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2689 &serial, NULL,
2690 NULL, NULL, 0);
2692 if (!status) {
2693 WCMD_print_error ();
2694 return 0;
2696 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2697 curdir[0], label, HIWORD(serial), LOWORD(serial));
2698 if (set_label) {
2699 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2700 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2701 if (count > 1) {
2702 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2703 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2705 if (strlenW(path) != 0) {
2706 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2708 else {
2709 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2712 return 1;
2715 /**************************************************************************
2716 * WCMD_exit
2718 * Exit either the process, or just this batch program
2722 void WCMD_exit (CMD_LIST **cmdList) {
2724 static const WCHAR parmB[] = {'/','B','\0'};
2725 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2727 if (context && lstrcmpiW(quals, parmB) == 0) {
2728 errorlevel = rc;
2729 context -> skip_rest = TRUE;
2730 *cmdList = NULL;
2731 } else {
2732 ExitProcess(rc);
2737 /*****************************************************************************
2738 * WCMD_assoc
2740 * Lists or sets file associations (assoc = TRUE)
2741 * Lists or sets file types (assoc = FALSE)
2743 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2745 HKEY key;
2746 DWORD accessOptions = KEY_READ;
2747 WCHAR *newValue;
2748 LONG rc = ERROR_SUCCESS;
2749 WCHAR keyValue[MAXSTRING];
2750 DWORD valueLen = MAXSTRING;
2751 HKEY readKey;
2752 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2753 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2755 /* See if parameter includes '=' */
2756 errorlevel = 0;
2757 newValue = strchrW(command, '=');
2758 if (newValue) accessOptions |= KEY_WRITE;
2760 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2761 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2762 accessOptions, &key) != ERROR_SUCCESS) {
2763 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2764 return;
2767 /* If no parameters then list all associations */
2768 if (*command == 0x00) {
2769 int index = 0;
2771 /* Enumerate all the keys */
2772 while (rc != ERROR_NO_MORE_ITEMS) {
2773 WCHAR keyName[MAXSTRING];
2774 DWORD nameLen;
2776 /* Find the next value */
2777 nameLen = MAXSTRING;
2778 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2780 if (rc == ERROR_SUCCESS) {
2782 /* Only interested in extension ones if assoc, or others
2783 if not assoc */
2784 if ((keyName[0] == '.' && assoc) ||
2785 (!(keyName[0] == '.') && (!assoc)))
2787 WCHAR subkey[MAXSTRING];
2788 strcpyW(subkey, keyName);
2789 if (!assoc) strcatW(subkey, shOpCmdW);
2791 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2793 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2794 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2795 WCMD_output_asis(keyName);
2796 WCMD_output_asis(equalW);
2797 /* If no default value found, leave line empty after '=' */
2798 if (rc == ERROR_SUCCESS) {
2799 WCMD_output_asis(keyValue);
2801 WCMD_output_asis(newline);
2802 RegCloseKey(readKey);
2808 } else {
2810 /* Parameter supplied - if no '=' on command line, its a query */
2811 if (newValue == NULL) {
2812 WCHAR *space;
2813 WCHAR subkey[MAXSTRING];
2815 /* Query terminates the parameter at the first space */
2816 strcpyW(keyValue, command);
2817 space = strchrW(keyValue, ' ');
2818 if (space) *space=0x00;
2820 /* Set up key name */
2821 strcpyW(subkey, keyValue);
2822 if (!assoc) strcatW(subkey, shOpCmdW);
2824 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2826 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2827 WCMD_output_asis(command);
2828 WCMD_output_asis(equalW);
2829 /* If no default value found, leave line empty after '=' */
2830 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2831 WCMD_output_asis(newline);
2832 RegCloseKey(readKey);
2834 } else {
2835 WCHAR msgbuffer[MAXSTRING];
2836 WCHAR outbuffer[MAXSTRING];
2838 /* Load the translated 'File association not found' */
2839 if (assoc) {
2840 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2841 } else {
2842 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2844 wsprintfW(outbuffer, msgbuffer, keyValue);
2845 WCMD_output_asis_stderr(outbuffer);
2846 errorlevel = 2;
2849 /* Not a query - its a set or clear of a value */
2850 } else {
2852 WCHAR subkey[MAXSTRING];
2854 /* Get pointer to new value */
2855 *newValue = 0x00;
2856 newValue++;
2858 /* Set up key name */
2859 strcpyW(subkey, command);
2860 if (!assoc) strcatW(subkey, shOpCmdW);
2862 /* If nothing after '=' then clear value - only valid for ASSOC */
2863 if (*newValue == 0x00) {
2865 if (assoc) rc = RegDeleteKeyW(key, command);
2866 if (assoc && rc == ERROR_SUCCESS) {
2867 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2869 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2870 WCMD_print_error();
2871 errorlevel = 2;
2873 } else {
2874 WCHAR msgbuffer[MAXSTRING];
2875 WCHAR outbuffer[MAXSTRING];
2877 /* Load the translated 'File association not found' */
2878 if (assoc) {
2879 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2880 sizeof(msgbuffer)/sizeof(WCHAR));
2881 } else {
2882 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2883 sizeof(msgbuffer)/sizeof(WCHAR));
2885 wsprintfW(outbuffer, msgbuffer, keyValue);
2886 WCMD_output_asis_stderr(outbuffer);
2887 errorlevel = 2;
2890 /* It really is a set value = contents */
2891 } else {
2892 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2893 accessOptions, NULL, &readKey, NULL);
2894 if (rc == ERROR_SUCCESS) {
2895 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2896 (LPBYTE)newValue,
2897 sizeof(WCHAR) * (strlenW(newValue) + 1));
2898 RegCloseKey(readKey);
2901 if (rc != ERROR_SUCCESS) {
2902 WCMD_print_error();
2903 errorlevel = 2;
2904 } else {
2905 WCMD_output_asis(command);
2906 WCMD_output_asis(equalW);
2907 WCMD_output_asis(newValue);
2908 WCMD_output_asis(newline);
2914 /* Clean up */
2915 RegCloseKey(key);
2918 /****************************************************************************
2919 * WCMD_color
2921 * Colors the terminal screen.
2924 void WCMD_color (void) {
2926 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2927 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2929 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2930 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
2931 return;
2934 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2936 COORD topLeft;
2937 DWORD screenSize;
2938 DWORD color = 0;
2940 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2942 topLeft.X = 0;
2943 topLeft.Y = 0;
2945 /* Convert the color hex digits */
2946 if (param1[0] == 0x00) {
2947 color = defaultColor;
2948 } else {
2949 color = strtoulW(param1, NULL, 16);
2952 /* Fail if fg == bg color */
2953 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2954 errorlevel = 1;
2955 return;
2958 /* Set the current screen contents and ensure all future writes
2959 remain this color */
2960 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2961 SetConsoleTextAttribute(hStdOut, color);