mscoree/tests: Mark a weird w2k3 result as broken.
[wine/multimedia.git] / programs / cmd / builtins.c
blob0352e59bcfe2a730f3febe1729eb8d8c7a83bfda
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, WCHAR *firstcmd, WCHAR *variable,
45 WCHAR *value, BOOL isIF, BOOL conditionTRUE);
47 struct env_stack *saved_environment;
48 struct env_stack *pushd_directories;
50 extern HINSTANCE hinst;
51 extern WCHAR inbuilt[][10];
52 extern int echo_mode, verify_mode, defaultColor;
53 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
54 extern BATCH_CONTEXT *context;
55 extern DWORD errorlevel;
57 static const WCHAR dotW[] = {'.','\0'};
58 static const WCHAR dotdotW[] = {'.','.','\0'};
59 static const WCHAR slashW[] = {'\\','\0'};
60 static const WCHAR starW[] = {'*','\0'};
61 static const WCHAR equalW[] = {'=','\0'};
62 static const WCHAR fslashW[] = {'/','\0'};
63 static const WCHAR onW[] = {'O','N','\0'};
64 static const WCHAR offW[] = {'O','F','F','\0'};
65 static const WCHAR parmY[] = {'/','Y','\0'};
66 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
67 static const WCHAR nullW[] = {'\0'};
69 /**************************************************************************
70 * WCMD_ask_confirm
72 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
73 * answer.
75 * Returns True if Y (or A) answer is selected
76 * If optionAll contains a pointer, ALL is allowed, and if answered
77 * set to TRUE
80 static BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
82 WCHAR msgbuffer[MAXSTRING];
83 WCHAR Ybuffer[MAXSTRING];
84 WCHAR Nbuffer[MAXSTRING];
85 WCHAR Abuffer[MAXSTRING];
86 WCHAR answer[MAX_PATH] = {'\0'};
87 DWORD count = 0;
89 /* Load the translated 'Are you sure', plus valid answers */
90 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
91 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
92 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
93 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
95 /* Loop waiting on a Y or N */
96 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
97 static const WCHAR startBkt[] = {' ','(','\0'};
98 static const WCHAR endBkt[] = {')','?','\0'};
100 WCMD_output_asis (message);
101 if (showSureText) {
102 WCMD_output_asis (msgbuffer);
104 WCMD_output_asis (startBkt);
105 WCMD_output_asis (Ybuffer);
106 WCMD_output_asis (fslashW);
107 WCMD_output_asis (Nbuffer);
108 if (optionAll) {
109 WCMD_output_asis (fslashW);
110 WCMD_output_asis (Abuffer);
112 WCMD_output_asis (endBkt);
113 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
114 sizeof(answer)/sizeof(WCHAR), &count, NULL);
115 answer[0] = toupperW(answer[0]);
118 /* Return the answer */
119 return ((answer[0] == Ybuffer[0]) ||
120 (optionAll && (answer[0] == Abuffer[0])));
123 /****************************************************************************
124 * WCMD_clear_screen
126 * Clear the terminal screen.
129 void WCMD_clear_screen (void) {
131 /* Emulate by filling the screen from the top left to bottom right with
132 spaces, then moving the cursor to the top left afterwards */
133 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
134 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
136 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
138 COORD topLeft;
139 DWORD screenSize;
141 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
143 topLeft.X = 0;
144 topLeft.Y = 0;
145 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
146 SetConsoleCursorPosition(hStdOut, topLeft);
150 /****************************************************************************
151 * WCMD_change_tty
153 * Change the default i/o device (ie redirect STDin/STDout).
156 void WCMD_change_tty (void) {
158 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
162 /****************************************************************************
163 * WCMD_choice
167 void WCMD_choice (WCHAR * command) {
169 static const WCHAR bellW[] = {7,0};
170 static const WCHAR commaW[] = {',',0};
171 static const WCHAR bracket_open[] = {'[',0};
172 static const WCHAR bracket_close[] = {']','?',0};
173 WCHAR answer[16];
174 WCHAR buffer[16];
175 WCHAR *ptr = NULL;
176 WCHAR *opt_c = NULL;
177 WCHAR *my_command = NULL;
178 WCHAR opt_default = 0;
179 DWORD opt_timeout = 0;
180 DWORD count;
181 DWORD oldmode;
182 DWORD have_console;
183 BOOL opt_n = FALSE;
184 BOOL opt_s = FALSE;
186 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
187 errorlevel = 0;
189 my_command = WCMD_strdupW(WCMD_strtrim_leading_spaces(command));
190 if (!my_command)
191 return;
193 ptr = WCMD_strtrim_leading_spaces(my_command);
194 while (*ptr == '/') {
195 switch (toupperW(ptr[1])) {
196 case 'C':
197 ptr += 2;
198 /* the colon is optional */
199 if (*ptr == ':')
200 ptr++;
202 if (!*ptr || isspaceW(*ptr)) {
203 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
204 HeapFree(GetProcessHeap(), 0, my_command);
205 return;
208 /* remember the allowed keys (overwrite previous /C option) */
209 opt_c = ptr;
210 while (*ptr && (!isspaceW(*ptr)))
211 ptr++;
213 if (*ptr) {
214 /* terminate allowed chars */
215 *ptr = 0;
216 ptr = WCMD_strtrim_leading_spaces(&ptr[1]);
218 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
219 break;
221 case 'N':
222 opt_n = TRUE;
223 ptr = WCMD_strtrim_leading_spaces(&ptr[2]);
224 break;
226 case 'S':
227 opt_s = TRUE;
228 ptr = WCMD_strtrim_leading_spaces(&ptr[2]);
229 break;
231 case 'T':
232 ptr = &ptr[2];
233 /* the colon is optional */
234 if (*ptr == ':')
235 ptr++;
237 opt_default = *ptr++;
239 if (!opt_default || (*ptr != ',')) {
240 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
241 HeapFree(GetProcessHeap(), 0, my_command);
242 return;
244 ptr++;
246 count = 0;
247 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
248 count++;
249 ptr++;
252 answer[count] = 0;
253 opt_timeout = atoiW(answer);
255 ptr = WCMD_strtrim_leading_spaces(ptr);
256 break;
258 default:
259 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
260 HeapFree(GetProcessHeap(), 0, my_command);
261 return;
265 if (opt_timeout)
266 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
268 if (have_console)
269 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
271 /* use default keys, when needed: localized versions of "Y"es and "No" */
272 if (!opt_c) {
273 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
274 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
275 opt_c = buffer;
276 buffer[2] = 0;
279 /* print the question, when needed */
280 if (*ptr)
281 WCMD_output_asis(ptr);
283 if (!opt_s) {
284 struprW(opt_c);
285 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
288 if (!opt_n) {
289 /* print a list of all allowed answers inside brackets */
290 WCMD_output_asis(bracket_open);
291 ptr = opt_c;
292 answer[1] = 0;
293 while ((answer[0] = *ptr++)) {
294 WCMD_output_asis(answer);
295 if (*ptr)
296 WCMD_output_asis(commaW);
298 WCMD_output_asis(bracket_close);
301 while (TRUE) {
303 /* FIXME: Add support for option /T */
304 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count, NULL);
306 if (!opt_s)
307 answer[0] = toupperW(answer[0]);
309 ptr = strchrW(opt_c, answer[0]);
310 if (ptr) {
311 WCMD_output_asis(answer);
312 WCMD_output(newline);
313 if (have_console)
314 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
316 errorlevel = (ptr - opt_c) + 1;
317 WINE_TRACE("answer: %d\n", errorlevel);
318 HeapFree(GetProcessHeap(), 0, my_command);
319 return;
321 else
323 /* key not allowed: play the bell */
324 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
325 WCMD_output_asis(bellW);
330 /****************************************************************************
331 * WCMD_copy
333 * Copy a file or wildcarded set.
334 * FIXME: Add support for a+b+c type syntax
337 void WCMD_copy (void) {
339 WIN32_FIND_DATAW fd;
340 HANDLE hff;
341 BOOL force, status;
342 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
343 DWORD len;
344 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
345 BOOL copyToDir = FALSE;
346 WCHAR srcspec[MAX_PATH];
347 DWORD attribs;
348 WCHAR drive[10];
349 WCHAR dir[MAX_PATH];
350 WCHAR fname[MAX_PATH];
351 WCHAR ext[MAX_PATH];
353 if (param1[0] == 0x00) {
354 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
355 return;
358 /* Convert source into full spec */
359 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
360 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
361 if (srcpath[strlenW(srcpath) - 1] == '\\')
362 srcpath[strlenW(srcpath) - 1] = '\0';
364 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
365 attribs = GetFileAttributesW(srcpath);
366 } else {
367 attribs = 0;
369 strcpyW(srcspec, srcpath);
371 /* If a directory, then add \* on the end when searching */
372 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
373 strcatW(srcpath, slashW);
374 strcatW(srcspec, slashW);
375 strcatW(srcspec, starW);
376 } else {
377 WCMD_splitpath(srcpath, drive, dir, fname, ext);
378 strcpyW(srcpath, drive);
379 strcatW(srcpath, dir);
382 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
384 /* If no destination supplied, assume current directory */
385 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
386 if (param2[0] == 0x00) {
387 strcpyW(param2, dotW);
390 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
391 if (outpath[strlenW(outpath) - 1] == '\\')
392 outpath[strlenW(outpath) - 1] = '\0';
393 attribs = GetFileAttributesW(outpath);
394 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
395 strcatW (outpath, slashW);
396 copyToDir = TRUE;
398 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
399 wine_dbgstr_w(outpath), copyToDir);
401 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
402 if (strstrW (quals, parmNoY))
403 force = FALSE;
404 else if (strstrW (quals, parmY))
405 force = TRUE;
406 else {
407 /* By default, we will force the overwrite in batch mode and ask for
408 * confirmation in interactive mode. */
409 force = !!context;
411 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
412 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
413 * default behavior. */
414 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
415 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
416 if (!lstrcmpiW (copycmd, parmY))
417 force = TRUE;
418 else if (!lstrcmpiW (copycmd, parmNoY))
419 force = FALSE;
423 /* Loop through all source files */
424 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
425 hff = FindFirstFileW(srcspec, &fd);
426 if (hff != INVALID_HANDLE_VALUE) {
427 do {
428 WCHAR outname[MAX_PATH];
429 WCHAR srcname[MAX_PATH];
430 BOOL overwrite = force;
432 /* Destination is either supplied filename, or source name in
433 supplied destination directory */
434 strcpyW(outname, outpath);
435 if (copyToDir) strcatW(outname, fd.cFileName);
436 strcpyW(srcname, srcpath);
437 strcatW(srcname, fd.cFileName);
439 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
440 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
442 /* Skip . and .., and directories */
443 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
444 overwrite = FALSE;
445 WINE_TRACE("Skipping directories\n");
448 /* Prompt before overwriting */
449 else if (!overwrite) {
450 attribs = GetFileAttributesW(outname);
451 if (attribs != INVALID_FILE_ATTRIBUTES) {
452 WCHAR buffer[MAXSTRING];
453 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
454 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
456 else overwrite = TRUE;
459 /* Do the copy as appropriate */
460 if (overwrite) {
461 status = CopyFileW(srcname, outname, FALSE);
462 if (!status) WCMD_print_error ();
465 } while (FindNextFileW(hff, &fd) != 0);
466 FindClose (hff);
467 } else {
468 status = ERROR_FILE_NOT_FOUND;
469 WCMD_print_error ();
473 /****************************************************************************
474 * WCMD_create_dir
476 * Create a directory.
478 * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
479 * they do not already exist.
482 static BOOL create_full_path(WCHAR* path)
484 int len;
485 WCHAR *new_path;
486 BOOL ret = TRUE;
488 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path)+1) * sizeof(WCHAR));
489 strcpyW(new_path,path);
491 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
492 new_path[len - 1] = 0;
494 while (!CreateDirectoryW(new_path,NULL))
496 WCHAR *slash;
497 DWORD last_error = GetLastError();
498 if (last_error == ERROR_ALREADY_EXISTS)
499 break;
501 if (last_error != ERROR_PATH_NOT_FOUND)
503 ret = FALSE;
504 break;
507 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
509 ret = FALSE;
510 break;
513 len = slash - new_path;
514 new_path[len] = 0;
515 if (!create_full_path(new_path))
517 ret = FALSE;
518 break;
520 new_path[len] = '\\';
522 HeapFree(GetProcessHeap(),0,new_path);
523 return ret;
526 void WCMD_create_dir (void) {
528 if (param1[0] == 0x00) {
529 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
530 return;
532 if (!create_full_path(param1)) WCMD_print_error ();
535 /* Parse the /A options given by the user on the commandline
536 * into a bitmask of wanted attributes (*wantSet),
537 * and a bitmask of unwanted attributes (*wantClear).
539 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
540 static const WCHAR parmA[] = {'/','A','\0'};
541 WCHAR *p;
543 /* both are strictly 'out' parameters */
544 *wantSet=0;
545 *wantClear=0;
547 /* For each /A argument */
548 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
549 /* Skip /A itself */
550 p += 2;
552 /* Skip optional : */
553 if (*p == ':') p++;
555 /* For each of the attribute specifier chars to this /A option */
556 for (; *p != 0 && *p != '/'; p++) {
557 BOOL negate = FALSE;
558 DWORD mask = 0;
560 if (*p == '-') {
561 negate=TRUE;
562 p++;
565 /* Convert the attribute specifier to a bit in one of the masks */
566 switch (*p) {
567 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
568 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
569 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
570 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
571 default:
572 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
574 if (negate)
575 *wantClear |= mask;
576 else
577 *wantSet |= mask;
582 /* If filename part of parameter is * or *.*,
583 * and neither /Q nor /P options were given,
584 * prompt the user whether to proceed.
585 * Returns FALSE if user says no, TRUE otherwise.
586 * *pPrompted is set to TRUE if the user is prompted.
587 * (If /P supplied, del will prompt for individual files later.)
589 static BOOL WCMD_delete_confirm_wildcard(WCHAR *filename, BOOL *pPrompted) {
590 static const WCHAR parmP[] = {'/','P','\0'};
591 static const WCHAR parmQ[] = {'/','Q','\0'};
593 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
594 static const WCHAR anyExt[]= {'.','*','\0'};
595 WCHAR drive[10];
596 WCHAR dir[MAX_PATH];
597 WCHAR fname[MAX_PATH];
598 WCHAR ext[MAX_PATH];
599 WCHAR fpath[MAX_PATH];
601 /* Convert path into actual directory spec */
602 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
603 WCMD_splitpath(fpath, drive, dir, fname, ext);
605 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
606 if ((strcmpW(fname, starW) == 0) &&
607 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
609 WCHAR question[MAXSTRING];
610 static const WCHAR fmt[] = {'%','s',' ','\0'};
612 /* Caller uses this to suppress "file not found" warning later */
613 *pPrompted = TRUE;
615 /* Ask for confirmation */
616 wsprintfW(question, fmt, fpath);
617 return WCMD_ask_confirm(question, TRUE, NULL);
620 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
621 return TRUE;
624 /* Helper function for WCMD_delete().
625 * Deletes a single file, directory, or wildcard.
626 * If /S was given, does it recursively.
627 * Returns TRUE if a file was deleted.
629 static BOOL WCMD_delete_one (WCHAR *thisArg) {
631 static const WCHAR parmP[] = {'/','P','\0'};
632 static const WCHAR parmS[] = {'/','S','\0'};
633 static const WCHAR parmF[] = {'/','F','\0'};
634 DWORD wanted_attrs;
635 DWORD unwanted_attrs;
636 BOOL found = FALSE;
637 WCHAR argCopy[MAX_PATH];
638 WIN32_FIND_DATAW fd;
639 HANDLE hff;
640 WCHAR fpath[MAX_PATH];
641 WCHAR *p;
642 BOOL handleParm = TRUE;
644 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
646 strcpyW(argCopy, thisArg);
647 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
648 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
650 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
651 /* Skip this arg if user declines to delete *.* */
652 return FALSE;
655 /* First, try to delete in the current directory */
656 hff = FindFirstFileW(argCopy, &fd);
657 if (hff == INVALID_HANDLE_VALUE) {
658 handleParm = FALSE;
659 } else {
660 found = TRUE;
663 /* Support del <dirname> by just deleting all files dirname\* */
664 if (handleParm
665 && (strchrW(argCopy,'*') == NULL)
666 && (strchrW(argCopy,'?') == NULL)
667 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
669 WCHAR modifiedParm[MAX_PATH];
670 static const WCHAR slashStar[] = {'\\','*','\0'};
672 strcpyW(modifiedParm, argCopy);
673 strcatW(modifiedParm, slashStar);
674 FindClose(hff);
675 found = TRUE;
676 WCMD_delete_one(modifiedParm);
678 } else if (handleParm) {
680 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
681 strcpyW (fpath, argCopy);
682 do {
683 p = strrchrW (fpath, '\\');
684 if (p != NULL) {
685 *++p = '\0';
686 strcatW (fpath, fd.cFileName);
688 else strcpyW (fpath, fd.cFileName);
689 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
690 BOOL ok;
692 /* Handle attribute matching (/A) */
693 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
694 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
696 /* /P means prompt for each file */
697 if (ok && strstrW (quals, parmP) != NULL) {
698 WCHAR question[MAXSTRING];
700 /* Ask for confirmation */
701 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
702 ok = WCMD_ask_confirm(question, FALSE, NULL);
705 /* Only proceed if ok to */
706 if (ok) {
708 /* If file is read only, and /A:r or /F supplied, delete it */
709 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
710 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
711 strstrW (quals, parmF) != NULL)) {
712 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
715 /* Now do the delete */
716 if (!DeleteFileW(fpath)) WCMD_print_error ();
720 } while (FindNextFileW(hff, &fd) != 0);
721 FindClose (hff);
724 /* Now recurse into all subdirectories handling the parameter in the same way */
725 if (strstrW (quals, parmS) != NULL) {
727 WCHAR thisDir[MAX_PATH];
728 int cPos;
730 WCHAR drive[10];
731 WCHAR dir[MAX_PATH];
732 WCHAR fname[MAX_PATH];
733 WCHAR ext[MAX_PATH];
735 /* Convert path into actual directory spec */
736 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
737 WCMD_splitpath(thisDir, drive, dir, fname, ext);
739 strcpyW(thisDir, drive);
740 strcatW(thisDir, dir);
741 cPos = strlenW(thisDir);
743 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
745 /* Append '*' to the directory */
746 thisDir[cPos] = '*';
747 thisDir[cPos+1] = 0x00;
749 hff = FindFirstFileW(thisDir, &fd);
751 /* Remove residual '*' */
752 thisDir[cPos] = 0x00;
754 if (hff != INVALID_HANDLE_VALUE) {
755 DIRECTORY_STACK *allDirs = NULL;
756 DIRECTORY_STACK *lastEntry = NULL;
758 do {
759 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
760 (strcmpW(fd.cFileName, dotdotW) != 0) &&
761 (strcmpW(fd.cFileName, dotW) != 0)) {
763 DIRECTORY_STACK *nextDir;
764 WCHAR subParm[MAX_PATH];
766 /* Work out search parameter in sub dir */
767 strcpyW (subParm, thisDir);
768 strcatW (subParm, fd.cFileName);
769 strcatW (subParm, slashW);
770 strcatW (subParm, fname);
771 strcatW (subParm, ext);
772 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
774 /* Allocate memory, add to list */
775 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
776 if (allDirs == NULL) allDirs = nextDir;
777 if (lastEntry != NULL) lastEntry->next = nextDir;
778 lastEntry = nextDir;
779 nextDir->next = NULL;
780 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
781 (strlenW(subParm)+1) * sizeof(WCHAR));
782 strcpyW(nextDir->dirName, subParm);
784 } while (FindNextFileW(hff, &fd) != 0);
785 FindClose (hff);
787 /* Go through each subdir doing the delete */
788 while (allDirs != NULL) {
789 DIRECTORY_STACK *tempDir;
791 tempDir = allDirs->next;
792 found |= WCMD_delete_one (allDirs->dirName);
794 HeapFree(GetProcessHeap(),0,allDirs->dirName);
795 HeapFree(GetProcessHeap(),0,allDirs);
796 allDirs = tempDir;
801 return found;
804 /****************************************************************************
805 * WCMD_delete
807 * Delete a file or wildcarded set.
809 * Note on /A:
810 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
811 * - Each set is a pattern, eg /ahr /as-r means
812 * readonly+hidden OR nonreadonly system files
813 * - The '-' applies to a single field, ie /a:-hr means read only
814 * non-hidden files
817 BOOL WCMD_delete (WCHAR *command) {
818 int argno;
819 WCHAR *argN;
820 BOOL argsProcessed = FALSE;
821 BOOL foundAny = FALSE;
823 errorlevel = 0;
825 for (argno=0; ; argno++) {
826 BOOL found;
827 WCHAR *thisArg;
829 argN = NULL;
830 thisArg = WCMD_parameter (command, argno, &argN);
831 if (!argN)
832 break; /* no more parameters */
833 if (argN[0] == '/')
834 continue; /* skip options */
836 argsProcessed = TRUE;
837 found = WCMD_delete_one(thisArg);
838 if (!found) {
839 errorlevel = 1;
840 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
842 foundAny |= found;
845 /* Handle no valid args */
846 if (!argsProcessed)
847 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
849 return foundAny;
852 /****************************************************************************
853 * WCMD_echo
855 * Echo input to the screen (or not). We don't try to emulate the bugs
856 * in DOS (try typing "ECHO ON AGAIN" for an example).
859 void WCMD_echo (const WCHAR *command) {
861 int count;
862 const WCHAR *origcommand = command;
864 if (command[0]==' ' || command[0]=='.')
865 command++;
866 count = strlenW(command);
867 if (count == 0 && origcommand[0]!='.') {
868 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
869 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
870 return;
872 if (lstrcmpiW(command, onW) == 0) {
873 echo_mode = 1;
874 return;
876 if (lstrcmpiW(command, offW) == 0) {
877 echo_mode = 0;
878 return;
880 WCMD_output_asis (command);
881 WCMD_output (newline);
885 /**************************************************************************
886 * WCMD_for
888 * Batch file loop processing.
890 * On entry: cmdList contains the syntax up to the set
891 * next cmdList and all in that bracket contain the set data
892 * next cmdlist contains the DO cmd
893 * following that is either brackets or && entries (as per if)
897 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
899 WIN32_FIND_DATAW fd;
900 HANDLE hff;
901 int i;
902 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
903 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
904 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
905 WCHAR variable[4];
906 WCHAR *firstCmd;
907 int thisDepth;
909 WCHAR *curPos = p;
910 BOOL expandDirs = FALSE;
911 BOOL useNumbers = FALSE;
912 BOOL doFileset = FALSE;
913 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
914 int itemNum;
915 CMD_LIST *thisCmdStart;
918 /* Handle optional qualifiers (multiple are allowed) */
919 while (*curPos && *curPos == '/') {
920 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
921 curPos++;
922 switch (toupperW(*curPos)) {
923 case 'D': curPos++; expandDirs = TRUE; break;
924 case 'L': curPos++; useNumbers = TRUE; break;
926 /* Recursive is special case - /R can have an optional path following it */
927 /* filenamesets are another special case - /F can have an optional options following it */
928 case 'R':
929 case 'F':
931 BOOL isRecursive = (*curPos == 'R');
933 if (!isRecursive)
934 doFileset = TRUE;
936 /* Skip whitespace */
937 curPos++;
938 while (*curPos && *curPos==' ') curPos++;
940 /* Next parm is either qualifier, path/options or variable -
941 only care about it if it is the path/options */
942 if (*curPos && *curPos != '/' && *curPos != '%') {
943 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
944 else WINE_FIXME("/F needs to handle options\n");
946 break;
948 default:
949 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
950 curPos++;
953 /* Skip whitespace between qualifiers */
954 while (*curPos && *curPos==' ') curPos++;
957 /* Skip whitespace before variable */
958 while (*curPos && *curPos==' ') curPos++;
960 /* Ensure line continues with variable */
961 if (!*curPos || *curPos != '%') {
962 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
963 return;
966 /* Variable should follow */
967 i = 0;
968 while (curPos[i] && curPos[i]!=' ') i++;
969 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
970 variable[i] = 0x00;
971 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
972 curPos = &curPos[i];
974 /* Skip whitespace before IN */
975 while (*curPos && *curPos==' ') curPos++;
977 /* Ensure line continues with IN */
978 if (!*curPos || lstrcmpiW (curPos, inW)) {
979 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
980 return;
983 /* Save away where the set of data starts and the variable */
984 thisDepth = (*cmdList)->bracketDepth;
985 *cmdList = (*cmdList)->nextcommand;
986 setStart = (*cmdList);
988 /* Skip until the close bracket */
989 WINE_TRACE("Searching %p as the set\n", *cmdList);
990 while (*cmdList &&
991 (*cmdList)->command != NULL &&
992 (*cmdList)->bracketDepth > thisDepth) {
993 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
994 *cmdList = (*cmdList)->nextcommand;
997 /* Skip the close bracket, if there is one */
998 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1000 /* Syntax error if missing close bracket, or nothing following it
1001 and once we have the complete set, we expect a DO */
1002 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
1003 if ((*cmdList == NULL) ||
1004 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1005 (*cmdList)->command, 3, doW, -1) != 2)) {
1006 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1007 return;
1010 /* Save away the starting position for the commands (and offset for the
1011 first one */
1012 cmdStart = *cmdList;
1013 cmdEnd = *cmdList;
1014 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1015 itemNum = 0;
1017 thisSet = setStart;
1018 /* Loop through all set entries */
1019 while (thisSet &&
1020 thisSet->command != NULL &&
1021 thisSet->bracketDepth >= thisDepth) {
1023 /* Loop through all entries on the same line */
1024 WCHAR *item;
1025 WCHAR *itemStart;
1027 WINE_TRACE("Processing for set %p\n", thisSet);
1028 i = 0;
1029 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
1032 * If the parameter within the set has a wildcard then search for matching files
1033 * otherwise do a literal substitution.
1035 static const WCHAR wildcards[] = {'*','?','\0'};
1036 thisCmdStart = cmdStart;
1038 itemNum++;
1039 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1041 if (!useNumbers && !doFileset) {
1042 if (strpbrkW (item, wildcards)) {
1043 hff = FindFirstFileW(item, &fd);
1044 if (hff != INVALID_HANDLE_VALUE) {
1045 do {
1046 BOOL isDirectory = FALSE;
1048 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1050 /* Handle as files or dirs appropriately, but ignore . and .. */
1051 if (isDirectory == expandDirs &&
1052 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1053 (strcmpW(fd.cFileName, dotW) != 0))
1055 thisCmdStart = cmdStart;
1056 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1057 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1058 fd.cFileName, FALSE, TRUE);
1061 } while (FindNextFileW(hff, &fd) != 0);
1062 FindClose (hff);
1064 } else {
1065 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1068 } else if (useNumbers) {
1069 /* Convert the first 3 numbers to signed longs and save */
1070 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1071 /* else ignore them! */
1073 /* Filesets - either a list of files, or a command to run and parse the output */
1074 } else if (doFileset && *itemStart != '"') {
1076 HANDLE input;
1077 WCHAR temp_file[MAX_PATH];
1079 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1080 wine_dbgstr_w(item));
1082 /* If backquote or single quote, we need to launch that command
1083 and parse the results - use a temporary file */
1084 if (*itemStart == '`' || *itemStart == '\'') {
1086 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1087 static const WCHAR redirOut[] = {'>','%','s','\0'};
1088 static const WCHAR cmdW[] = {'C','M','D','\0'};
1090 /* Remove trailing character */
1091 itemStart[strlenW(itemStart)-1] = 0x00;
1093 /* Get temp filename */
1094 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1095 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1097 /* Execute program and redirect output */
1098 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1099 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1101 /* Open the file, read line by line and process */
1102 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1103 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1104 } else {
1106 /* Open the file, read line by line and process */
1107 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1108 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1111 /* Process the input file */
1112 if (input == INVALID_HANDLE_VALUE) {
1113 WCMD_print_error ();
1114 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1115 errorlevel = 1;
1116 return; /* FOR loop aborts at first failure here */
1118 } else {
1120 WCHAR buffer[MAXSTRING] = {'\0'};
1121 WCHAR *where, *parm;
1123 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1125 /* Skip blank lines*/
1126 parm = WCMD_parameter (buffer, 0, &where);
1127 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1128 wine_dbgstr_w(buffer));
1130 if (where) {
1131 /* FIXME: The following should be moved into its own routine and
1132 reused for the string literal parsing below */
1133 thisCmdStart = cmdStart;
1134 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1135 cmdEnd = thisCmdStart;
1138 buffer[0] = 0x00;
1141 CloseHandle (input);
1144 /* Delete the temporary file */
1145 if (*itemStart == '`' || *itemStart == '\'') {
1146 DeleteFileW(temp_file);
1149 /* Filesets - A string literal */
1150 } else if (doFileset && *itemStart == '"') {
1151 WCHAR buffer[MAXSTRING] = {'\0'};
1152 WCHAR *where, *parm;
1154 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1155 strcpyW(buffer, item);
1156 parm = WCMD_parameter (buffer, 0, &where);
1157 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1158 wine_dbgstr_w(buffer));
1160 if (where) {
1161 /* FIXME: The following should be moved into its own routine and
1162 reused for the string literal parsing below */
1163 thisCmdStart = cmdStart;
1164 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1165 cmdEnd = thisCmdStart;
1169 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1170 cmdEnd = thisCmdStart;
1171 i++;
1174 /* Move onto the next set line */
1175 thisSet = thisSet->nextcommand;
1178 /* If /L is provided, now run the for loop */
1179 if (useNumbers) {
1180 WCHAR thisNum[20];
1181 static const WCHAR fmt[] = {'%','d','\0'};
1183 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1184 numbers[0], numbers[2], numbers[1]);
1185 for (i=numbers[0];
1186 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1187 i=i + numbers[1]) {
1189 sprintfW(thisNum, fmt, i);
1190 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1192 thisCmdStart = cmdStart;
1193 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1194 cmdEnd = thisCmdStart;
1198 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1199 all processing, OR it should be pointing to the end of && processing OR
1200 it should be pointing at the NULL end of bracket for the DO. The return
1201 value needs to be the NEXT command to execute, which it either is, or
1202 we need to step over the closing bracket */
1203 *cmdList = cmdEnd;
1204 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1208 /*****************************************************************************
1209 * WCMD_part_execute
1211 * Execute a command, and any && or bracketed follow on to the command. The
1212 * first command to be executed may not be at the front of the
1213 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1215 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1216 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1218 CMD_LIST *curPosition = *cmdList;
1219 int myDepth = (*cmdList)->bracketDepth;
1221 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1222 cmdList, wine_dbgstr_w(firstcmd),
1223 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1224 conditionTRUE);
1226 /* Skip leading whitespace between condition and the command */
1227 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1229 /* Process the first command, if there is one */
1230 if (conditionTRUE && firstcmd && *firstcmd) {
1231 WCHAR *command = WCMD_strdupW(firstcmd);
1232 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1233 HeapFree(GetProcessHeap(), 0, command);
1237 /* If it didn't move the position, step to next command */
1238 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1240 /* Process any other parts of the command */
1241 if (*cmdList) {
1242 BOOL processThese = TRUE;
1244 if (isIF) processThese = conditionTRUE;
1246 while (*cmdList) {
1247 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1249 /* execute all appropriate commands */
1250 curPosition = *cmdList;
1252 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1253 *cmdList,
1254 (*cmdList)->prevDelim,
1255 (*cmdList)->bracketDepth, myDepth);
1257 /* Execute any statements appended to the line */
1258 /* FIXME: Only if previous call worked for && or failed for || */
1259 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1260 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1261 if (processThese && (*cmdList)->command) {
1262 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1263 value, cmdList);
1265 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1267 /* Execute any appended to the statement with (...) */
1268 } else if ((*cmdList)->bracketDepth > myDepth) {
1269 if (processThese) {
1270 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1271 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1273 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1275 /* End of the command - does 'ELSE ' follow as the next command? */
1276 } else {
1277 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1278 NORM_IGNORECASE | SORT_STRINGSORT,
1279 (*cmdList)->command, 5, ifElse, -1) == 2) {
1281 /* Swap between if and else processing */
1282 processThese = !processThese;
1284 /* Process the ELSE part */
1285 if (processThese) {
1286 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1288 /* Skip leading whitespace between condition and the command */
1289 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1290 if (*cmd) {
1291 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1294 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1295 } else {
1296 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1297 break;
1302 return;
1305 /**************************************************************************
1306 * WCMD_give_help
1308 * Simple on-line help. Help text is stored in the resource file.
1311 void WCMD_give_help (WCHAR *command) {
1313 int i;
1315 command = WCMD_strtrim_leading_spaces(command);
1316 if (strlenW(command) == 0) {
1317 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1319 else {
1320 for (i=0; i<=WCMD_EXIT; i++) {
1321 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1322 command, -1, inbuilt[i], -1) == 2) {
1323 WCMD_output_asis (WCMD_LoadMessage(i));
1324 return;
1327 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1329 return;
1332 /****************************************************************************
1333 * WCMD_go_to
1335 * Batch file jump instruction. Not the most efficient algorithm ;-)
1336 * Prints error message if the specified label cannot be found - the file pointer is
1337 * then at EOF, effectively stopping the batch file.
1338 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1341 void WCMD_goto (CMD_LIST **cmdList) {
1343 WCHAR string[MAX_PATH];
1344 WCHAR current[MAX_PATH];
1346 /* Do not process any more parts of a processed multipart or multilines command */
1347 if (cmdList) *cmdList = NULL;
1349 if (param1[0] == 0x00) {
1350 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1351 return;
1353 if (context != NULL) {
1354 WCHAR *paramStart = param1, *str;
1355 static const WCHAR eofW[] = {':','e','o','f','\0'};
1357 /* Handle special :EOF label */
1358 if (lstrcmpiW (eofW, param1) == 0) {
1359 context -> skip_rest = TRUE;
1360 return;
1363 /* Support goto :label as well as goto label */
1364 if (*paramStart == ':') paramStart++;
1366 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1367 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1368 str = string;
1369 while (isspaceW (*str)) str++;
1370 if (*str == ':') {
1371 DWORD index = 0;
1372 str++;
1373 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1374 index++;
1376 /* ignore space at the end */
1377 current[index] = 0;
1378 if (lstrcmpiW (current, paramStart) == 0) return;
1381 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1383 return;
1386 /*****************************************************************************
1387 * WCMD_pushd
1389 * Push a directory onto the stack
1392 void WCMD_pushd (WCHAR *command) {
1393 struct env_stack *curdir;
1394 WCHAR *thisdir;
1395 static const WCHAR parmD[] = {'/','D','\0'};
1397 if (strchrW(command, '/') != NULL) {
1398 SetLastError(ERROR_INVALID_PARAMETER);
1399 WCMD_print_error();
1400 return;
1403 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1404 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1405 if( !curdir || !thisdir ) {
1406 LocalFree(curdir);
1407 LocalFree(thisdir);
1408 WINE_ERR ("out of memory\n");
1409 return;
1412 /* Change directory using CD code with /D parameter */
1413 strcpyW(quals, parmD);
1414 GetCurrentDirectoryW (1024, thisdir);
1415 errorlevel = 0;
1416 WCMD_setshow_default(command);
1417 if (errorlevel) {
1418 LocalFree(curdir);
1419 LocalFree(thisdir);
1420 return;
1421 } else {
1422 curdir -> next = pushd_directories;
1423 curdir -> strings = thisdir;
1424 if (pushd_directories == NULL) {
1425 curdir -> u.stackdepth = 1;
1426 } else {
1427 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1429 pushd_directories = curdir;
1434 /*****************************************************************************
1435 * WCMD_popd
1437 * Pop a directory from the stack
1440 void WCMD_popd (void) {
1441 struct env_stack *temp = pushd_directories;
1443 if (!pushd_directories)
1444 return;
1446 /* pop the old environment from the stack, and make it the current dir */
1447 pushd_directories = temp->next;
1448 SetCurrentDirectoryW(temp->strings);
1449 LocalFree (temp->strings);
1450 LocalFree (temp);
1453 /****************************************************************************
1454 * WCMD_if
1456 * Batch file conditional.
1458 * On entry, cmdlist will point to command containing the IF, and optionally
1459 * the first command to execute (if brackets not found)
1460 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1461 * If ('s were found, execute all within that bracket
1462 * Command may optionally be followed by an ELSE - need to skip instructions
1463 * in the else using the same logic
1465 * FIXME: Much more syntax checking needed!
1468 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1470 int negate = 0, test = 0;
1471 WCHAR condition[MAX_PATH], *command, *s;
1472 static const WCHAR notW[] = {'n','o','t','\0'};
1473 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1474 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1475 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1476 static const WCHAR eqeqW[] = {'=','=','\0'};
1477 static const WCHAR parmI[] = {'/','I','\0'};
1479 if (!lstrcmpiW (param1, notW)) {
1480 negate = 1;
1481 strcpyW (condition, param2);
1483 else {
1484 strcpyW (condition, param1);
1486 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1488 if (!lstrcmpiW (condition, errlvlW)) {
1489 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1490 WCMD_parameter (p, 2+negate, &command);
1492 else if (!lstrcmpiW (condition, existW)) {
1493 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1494 test = 1;
1496 WCMD_parameter (p, 2+negate, &command);
1498 else if (!lstrcmpiW (condition, defdW)) {
1499 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1500 test = 1;
1502 WCMD_parameter (p, 2+negate, &command);
1504 else if ((s = strstrW (p, eqeqW))) {
1505 s += 2;
1506 if (strstrW (quals, parmI) == NULL) {
1507 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1509 else {
1510 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1512 WCMD_parameter (s, 1, &command);
1514 else {
1515 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1516 return;
1519 /* Process rest of IF statement which is on the same line
1520 Note: This may process all or some of the cmdList (eg a GOTO) */
1521 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1524 /****************************************************************************
1525 * WCMD_move
1527 * Move a file, directory tree or wildcarded set of files.
1530 void WCMD_move (void) {
1532 int status;
1533 WIN32_FIND_DATAW fd;
1534 HANDLE hff;
1535 WCHAR input[MAX_PATH];
1536 WCHAR output[MAX_PATH];
1537 WCHAR drive[10];
1538 WCHAR dir[MAX_PATH];
1539 WCHAR fname[MAX_PATH];
1540 WCHAR ext[MAX_PATH];
1542 if (param1[0] == 0x00) {
1543 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1544 return;
1547 /* If no destination supplied, assume current directory */
1548 if (param2[0] == 0x00) {
1549 strcpyW(param2, dotW);
1552 /* If 2nd parm is directory, then use original filename */
1553 /* Convert partial path to full path */
1554 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1555 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1556 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1557 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1559 /* Split into components */
1560 WCMD_splitpath(input, drive, dir, fname, ext);
1562 hff = FindFirstFileW(input, &fd);
1563 while (hff != INVALID_HANDLE_VALUE) {
1564 WCHAR dest[MAX_PATH];
1565 WCHAR src[MAX_PATH];
1566 DWORD attribs;
1568 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1570 /* Build src & dest name */
1571 strcpyW(src, drive);
1572 strcatW(src, dir);
1574 /* See if dest is an existing directory */
1575 attribs = GetFileAttributesW(output);
1576 if (attribs != INVALID_FILE_ATTRIBUTES &&
1577 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1578 strcpyW(dest, output);
1579 strcatW(dest, slashW);
1580 strcatW(dest, fd.cFileName);
1581 } else {
1582 strcpyW(dest, output);
1585 strcatW(src, fd.cFileName);
1587 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1588 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1590 /* Check if file is read only, otherwise move it */
1591 attribs = GetFileAttributesW(src);
1592 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1593 (attribs & FILE_ATTRIBUTE_READONLY)) {
1594 SetLastError(ERROR_ACCESS_DENIED);
1595 status = 0;
1596 } else {
1597 BOOL ok = TRUE;
1599 /* If destination exists, prompt unless /Y supplied */
1600 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1601 BOOL force = FALSE;
1602 WCHAR copycmd[MAXSTRING];
1603 int len;
1605 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1606 if (strstrW (quals, parmNoY))
1607 force = FALSE;
1608 else if (strstrW (quals, parmY))
1609 force = TRUE;
1610 else {
1611 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1612 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1613 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1614 && ! lstrcmpiW (copycmd, parmY));
1617 /* Prompt if overwriting */
1618 if (!force) {
1619 WCHAR question[MAXSTRING];
1620 WCHAR yesChar[10];
1622 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1624 /* Ask for confirmation */
1625 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1626 ok = WCMD_ask_confirm(question, FALSE, NULL);
1628 /* So delete the destination prior to the move */
1629 if (ok) {
1630 if (!DeleteFileW(dest)) {
1631 WCMD_print_error ();
1632 errorlevel = 1;
1633 ok = FALSE;
1639 if (ok) {
1640 status = MoveFileW(src, dest);
1641 } else {
1642 status = 1; /* Anything other than 0 to prevent error msg below */
1646 if (!status) {
1647 WCMD_print_error ();
1648 errorlevel = 1;
1651 /* Step on to next match */
1652 if (FindNextFileW(hff, &fd) == 0) {
1653 FindClose(hff);
1654 hff = INVALID_HANDLE_VALUE;
1655 break;
1660 /****************************************************************************
1661 * WCMD_pause
1663 * Wait for keyboard input.
1666 void WCMD_pause (void) {
1668 DWORD count;
1669 WCHAR string[32];
1671 WCMD_output (anykey);
1672 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1673 sizeof(string)/sizeof(WCHAR), &count, NULL);
1676 /****************************************************************************
1677 * WCMD_remove_dir
1679 * Delete a directory.
1682 void WCMD_remove_dir (WCHAR *command) {
1684 int argno = 0;
1685 int argsProcessed = 0;
1686 WCHAR *argN = command;
1687 static const WCHAR parmS[] = {'/','S','\0'};
1688 static const WCHAR parmQ[] = {'/','Q','\0'};
1690 /* Loop through all args */
1691 while (argN) {
1692 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1693 if (argN && argN[0] != '/') {
1694 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1695 wine_dbgstr_w(quals));
1696 argsProcessed++;
1698 /* If subdirectory search not supplied, just try to remove
1699 and report error if it fails (eg if it contains a file) */
1700 if (strstrW (quals, parmS) == NULL) {
1701 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1703 /* Otherwise use ShFileOp to recursively remove a directory */
1704 } else {
1706 SHFILEOPSTRUCTW lpDir;
1708 /* Ask first */
1709 if (strstrW (quals, parmQ) == NULL) {
1710 BOOL ok;
1711 WCHAR question[MAXSTRING];
1712 static const WCHAR fmt[] = {'%','s',' ','\0'};
1714 /* Ask for confirmation */
1715 wsprintfW(question, fmt, thisArg);
1716 ok = WCMD_ask_confirm(question, TRUE, NULL);
1718 /* Abort if answer is 'N' */
1719 if (!ok) return;
1722 /* Do the delete */
1723 lpDir.hwnd = NULL;
1724 lpDir.pTo = NULL;
1725 lpDir.pFrom = thisArg;
1726 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1727 lpDir.wFunc = FO_DELETE;
1728 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1733 /* Handle no valid args */
1734 if (argsProcessed == 0) {
1735 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1736 return;
1741 /****************************************************************************
1742 * WCMD_rename
1744 * Rename a file.
1747 void WCMD_rename (void) {
1749 int status;
1750 HANDLE hff;
1751 WIN32_FIND_DATAW fd;
1752 WCHAR input[MAX_PATH];
1753 WCHAR *dotDst = NULL;
1754 WCHAR drive[10];
1755 WCHAR dir[MAX_PATH];
1756 WCHAR fname[MAX_PATH];
1757 WCHAR ext[MAX_PATH];
1758 DWORD attribs;
1760 errorlevel = 0;
1762 /* Must be at least two args */
1763 if (param1[0] == 0x00 || param2[0] == 0x00) {
1764 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1765 errorlevel = 1;
1766 return;
1769 /* Destination cannot contain a drive letter or directory separator */
1770 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1771 SetLastError(ERROR_INVALID_PARAMETER);
1772 WCMD_print_error();
1773 errorlevel = 1;
1774 return;
1777 /* Convert partial path to full path */
1778 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1779 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1780 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1781 dotDst = strchrW(param2, '.');
1783 /* Split into components */
1784 WCMD_splitpath(input, drive, dir, fname, ext);
1786 hff = FindFirstFileW(input, &fd);
1787 while (hff != INVALID_HANDLE_VALUE) {
1788 WCHAR dest[MAX_PATH];
1789 WCHAR src[MAX_PATH];
1790 WCHAR *dotSrc = NULL;
1791 int dirLen;
1793 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1795 /* FIXME: If dest name or extension is *, replace with filename/ext
1796 part otherwise use supplied name. This supports:
1797 ren *.fred *.jim
1798 ren jim.* fred.* etc
1799 However, windows has a more complex algorithm supporting eg
1800 ?'s and *'s mid name */
1801 dotSrc = strchrW(fd.cFileName, '.');
1803 /* Build src & dest name */
1804 strcpyW(src, drive);
1805 strcatW(src, dir);
1806 strcpyW(dest, src);
1807 dirLen = strlenW(src);
1808 strcatW(src, fd.cFileName);
1810 /* Build name */
1811 if (param2[0] == '*') {
1812 strcatW(dest, fd.cFileName);
1813 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1814 } else {
1815 strcatW(dest, param2);
1816 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1819 /* Build Extension */
1820 if (dotDst && (*(dotDst+1)=='*')) {
1821 if (dotSrc) strcatW(dest, dotSrc);
1822 } else if (dotDst) {
1823 if (dotDst) strcatW(dest, dotDst);
1826 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1827 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1829 /* Check if file is read only, otherwise move it */
1830 attribs = GetFileAttributesW(src);
1831 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1832 (attribs & FILE_ATTRIBUTE_READONLY)) {
1833 SetLastError(ERROR_ACCESS_DENIED);
1834 status = 0;
1835 } else {
1836 status = MoveFileW(src, dest);
1839 if (!status) {
1840 WCMD_print_error ();
1841 errorlevel = 1;
1844 /* Step on to next match */
1845 if (FindNextFileW(hff, &fd) == 0) {
1846 FindClose(hff);
1847 hff = INVALID_HANDLE_VALUE;
1848 break;
1853 /*****************************************************************************
1854 * WCMD_dupenv
1856 * Make a copy of the environment.
1858 static WCHAR *WCMD_dupenv( const WCHAR *env )
1860 WCHAR *env_copy;
1861 int len;
1863 if( !env )
1864 return NULL;
1866 len = 0;
1867 while ( env[len] )
1868 len += (strlenW(&env[len]) + 1);
1870 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1871 if (!env_copy)
1873 WINE_ERR("out of memory\n");
1874 return env_copy;
1876 memcpy (env_copy, env, len*sizeof (WCHAR));
1877 env_copy[len] = 0;
1879 return env_copy;
1882 /*****************************************************************************
1883 * WCMD_setlocal
1885 * setlocal pushes the environment onto a stack
1886 * Save the environment as unicode so we don't screw anything up.
1888 void WCMD_setlocal (const WCHAR *s) {
1889 WCHAR *env;
1890 struct env_stack *env_copy;
1891 WCHAR cwd[MAX_PATH];
1893 /* DISABLEEXTENSIONS ignored */
1895 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1896 if( !env_copy )
1898 WINE_ERR ("out of memory\n");
1899 return;
1902 env = GetEnvironmentStringsW ();
1904 env_copy->strings = WCMD_dupenv (env);
1905 if (env_copy->strings)
1907 env_copy->next = saved_environment;
1908 saved_environment = env_copy;
1910 /* Save the current drive letter */
1911 GetCurrentDirectoryW(MAX_PATH, cwd);
1912 env_copy->u.cwd = cwd[0];
1914 else
1915 LocalFree (env_copy);
1917 FreeEnvironmentStringsW (env);
1921 /*****************************************************************************
1922 * WCMD_endlocal
1924 * endlocal pops the environment off a stack
1925 * Note: When searching for '=', search from WCHAR position 1, to handle
1926 * special internal environment variables =C:, =D: etc
1928 void WCMD_endlocal (void) {
1929 WCHAR *env, *old, *p;
1930 struct env_stack *temp;
1931 int len, n;
1933 if (!saved_environment)
1934 return;
1936 /* pop the old environment from the stack */
1937 temp = saved_environment;
1938 saved_environment = temp->next;
1940 /* delete the current environment, totally */
1941 env = GetEnvironmentStringsW ();
1942 old = WCMD_dupenv (GetEnvironmentStringsW ());
1943 len = 0;
1944 while (old[len]) {
1945 n = strlenW(&old[len]) + 1;
1946 p = strchrW(&old[len] + 1, '=');
1947 if (p)
1949 *p++ = 0;
1950 SetEnvironmentVariableW (&old[len], NULL);
1952 len += n;
1954 LocalFree (old);
1955 FreeEnvironmentStringsW (env);
1957 /* restore old environment */
1958 env = temp->strings;
1959 len = 0;
1960 while (env[len]) {
1961 n = strlenW(&env[len]) + 1;
1962 p = strchrW(&env[len] + 1, '=');
1963 if (p)
1965 *p++ = 0;
1966 SetEnvironmentVariableW (&env[len], p);
1968 len += n;
1971 /* Restore current drive letter */
1972 if (IsCharAlphaW(temp->u.cwd)) {
1973 WCHAR envvar[4];
1974 WCHAR cwd[MAX_PATH];
1975 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1977 wsprintfW(envvar, fmt, temp->u.cwd);
1978 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1979 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1980 SetCurrentDirectoryW(cwd);
1984 LocalFree (env);
1985 LocalFree (temp);
1988 /*****************************************************************************
1989 * WCMD_setshow_attrib
1991 * Display and optionally sets DOS attributes on a file or directory
1995 void WCMD_setshow_attrib (void) {
1997 DWORD count;
1998 HANDLE hff;
1999 WIN32_FIND_DATAW fd;
2000 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
2001 WCHAR *name = param1;
2002 DWORD attrib_set=0;
2003 DWORD attrib_clear=0;
2005 if (param1[0] == '+' || param1[0] == '-') {
2006 DWORD attrib = 0;
2007 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
2008 switch (param1[1]) {
2009 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
2010 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
2011 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
2012 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
2013 default:
2014 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2015 return;
2017 switch (param1[0]) {
2018 case '+': attrib_set = attrib; break;
2019 case '-': attrib_clear = attrib; break;
2021 name = param2;
2024 if (strlenW(name) == 0) {
2025 static const WCHAR slashStarW[] = {'\\','*','\0'};
2027 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
2028 strcatW (name, slashStarW);
2031 hff = FindFirstFileW(name, &fd);
2032 if (hff == INVALID_HANDLE_VALUE) {
2033 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
2035 else {
2036 do {
2037 if (attrib_set || attrib_clear) {
2038 fd.dwFileAttributes &= ~attrib_clear;
2039 fd.dwFileAttributes |= attrib_set;
2040 if (!fd.dwFileAttributes)
2041 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
2042 SetFileAttributesW(name, fd.dwFileAttributes);
2043 } else {
2044 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
2045 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
2046 flags[0] = 'H';
2048 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
2049 flags[1] = 'S';
2051 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
2052 flags[2] = 'A';
2054 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
2055 flags[3] = 'R';
2057 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
2058 flags[4] = 'T';
2060 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
2061 flags[5] = 'C';
2063 WCMD_output (fmt, flags, fd.cFileName);
2064 for (count=0; count < 8; count++) flags[count] = ' ';
2066 } while (FindNextFileW(hff, &fd) != 0);
2068 FindClose (hff);
2071 /*****************************************************************************
2072 * WCMD_setshow_default
2074 * Set/Show the current default directory
2077 void WCMD_setshow_default (WCHAR *command) {
2079 BOOL status;
2080 WCHAR string[1024];
2081 WCHAR cwd[1024];
2082 WCHAR *pos;
2083 WIN32_FIND_DATAW fd;
2084 HANDLE hff;
2085 static const WCHAR parmD[] = {'/','D','\0'};
2087 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2089 /* Skip /D and trailing whitespace if on the front of the command line */
2090 if (CompareStringW(LOCALE_USER_DEFAULT,
2091 NORM_IGNORECASE | SORT_STRINGSORT,
2092 command, 2, parmD, -1) == 2) {
2093 command += 2;
2094 while (*command && *command==' ') command++;
2097 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2098 if (strlenW(command) == 0) {
2099 strcatW (cwd, newline);
2100 WCMD_output (cwd);
2102 else {
2103 /* Remove any double quotes, which may be in the
2104 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2105 pos = string;
2106 while (*command) {
2107 if (*command != '"') *pos++ = *command;
2108 command++;
2110 *pos = 0x00;
2112 /* Search for appropriate directory */
2113 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2114 hff = FindFirstFileW(string, &fd);
2115 while (hff != INVALID_HANDLE_VALUE) {
2116 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2117 WCHAR fpath[MAX_PATH];
2118 WCHAR drive[10];
2119 WCHAR dir[MAX_PATH];
2120 WCHAR fname[MAX_PATH];
2121 WCHAR ext[MAX_PATH];
2122 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2124 /* Convert path into actual directory spec */
2125 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2126 WCMD_splitpath(fpath, drive, dir, fname, ext);
2128 /* Rebuild path */
2129 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2131 FindClose(hff);
2132 hff = INVALID_HANDLE_VALUE;
2133 break;
2136 /* Step on to next match */
2137 if (FindNextFileW(hff, &fd) == 0) {
2138 FindClose(hff);
2139 hff = INVALID_HANDLE_VALUE;
2140 break;
2144 /* Change to that directory */
2145 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2147 status = SetCurrentDirectoryW(string);
2148 if (!status) {
2149 errorlevel = 1;
2150 WCMD_print_error ();
2151 return;
2152 } else {
2154 /* Save away the actual new directory, to store as current location */
2155 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2157 /* Restore old directory if drive letter would change, and
2158 CD x:\directory /D (or pushd c:\directory) not supplied */
2159 if ((strstrW(quals, parmD) == NULL) &&
2160 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2161 SetCurrentDirectoryW(cwd);
2165 /* Set special =C: type environment variable, for drive letter of
2166 change of directory, even if path was restored due to missing
2167 /D (allows changing drive letter when not resident on that
2168 drive */
2169 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2170 WCHAR env[4];
2171 strcpyW(env, equalW);
2172 memcpy(env+1, string, 2 * sizeof(WCHAR));
2173 env[3] = 0x00;
2174 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2175 SetEnvironmentVariableW(env, string);
2179 return;
2182 /****************************************************************************
2183 * WCMD_setshow_date
2185 * Set/Show the system date
2186 * FIXME: Can't change date yet
2189 void WCMD_setshow_date (void) {
2191 WCHAR curdate[64], buffer[64];
2192 DWORD count;
2193 static const WCHAR parmT[] = {'/','T','\0'};
2195 if (strlenW(param1) == 0) {
2196 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2197 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2198 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2199 if (strstrW (quals, parmT) == NULL) {
2200 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2201 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2202 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2203 if (count > 2) {
2204 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2208 else WCMD_print_error ();
2210 else {
2211 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2215 /****************************************************************************
2216 * WCMD_compare
2218 static int WCMD_compare( const void *a, const void *b )
2220 int r;
2221 const WCHAR * const *str_a = a, * const *str_b = b;
2222 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2223 *str_a, -1, *str_b, -1 );
2224 if( r == CSTR_LESS_THAN ) return -1;
2225 if( r == CSTR_GREATER_THAN ) return 1;
2226 return 0;
2229 /****************************************************************************
2230 * WCMD_setshow_sortenv
2232 * sort variables into order for display
2233 * Optionally only display those who start with a stub
2234 * returns the count displayed
2236 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2238 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2239 const WCHAR **str;
2241 if (stub) stublen = strlenW(stub);
2243 /* count the number of strings, and the total length */
2244 while ( s[len] ) {
2245 len += (strlenW(&s[len]) + 1);
2246 count++;
2249 /* add the strings to an array */
2250 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2251 if( !str )
2252 return 0;
2253 str[0] = s;
2254 for( i=1; i<count; i++ )
2255 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2257 /* sort the array */
2258 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2260 /* print it */
2261 for( i=0; i<count; i++ ) {
2262 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2263 NORM_IGNORECASE | SORT_STRINGSORT,
2264 str[i], stublen, stub, -1) == 2) {
2265 /* Don't display special internal variables */
2266 if (str[i][0] != '=') {
2267 WCMD_output_asis(str[i]);
2268 WCMD_output_asis(newline);
2269 displayedcount++;
2274 LocalFree( str );
2275 return displayedcount;
2278 /****************************************************************************
2279 * WCMD_setshow_env
2281 * Set/Show the environment variables
2284 void WCMD_setshow_env (WCHAR *s) {
2286 LPVOID env;
2287 WCHAR *p;
2288 int status;
2289 static const WCHAR parmP[] = {'/','P','\0'};
2291 if (param1[0] == 0x00 && quals[0] == 0x00) {
2292 env = GetEnvironmentStringsW();
2293 WCMD_setshow_sortenv( env, NULL );
2294 return;
2297 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2298 if (CompareStringW(LOCALE_USER_DEFAULT,
2299 NORM_IGNORECASE | SORT_STRINGSORT,
2300 s, 2, parmP, -1) == 2) {
2301 WCHAR string[MAXSTRING];
2302 DWORD count;
2304 s += 2;
2305 while (*s && *s==' ') s++;
2306 if (*s=='\"')
2307 WCMD_opt_s_strip_quotes(s);
2309 /* If no parameter, or no '=' sign, return an error */
2310 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2311 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2312 return;
2315 /* Output the prompt */
2316 *p++ = '\0';
2317 if (strlenW(p) != 0) WCMD_output(p);
2319 /* Read the reply */
2320 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2321 sizeof(string)/sizeof(WCHAR), &count, NULL);
2322 if (count > 1) {
2323 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2324 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2325 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2326 wine_dbgstr_w(string));
2327 status = SetEnvironmentVariableW(s, string);
2330 } else {
2331 DWORD gle;
2333 if (*s=='\"')
2334 WCMD_opt_s_strip_quotes(s);
2335 p = strchrW (s, '=');
2336 if (p == NULL) {
2337 env = GetEnvironmentStringsW();
2338 if (WCMD_setshow_sortenv( env, s ) == 0) {
2339 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2340 errorlevel = 1;
2342 return;
2344 *p++ = '\0';
2346 if (strlenW(p) == 0) p = NULL;
2347 status = SetEnvironmentVariableW(s, p);
2348 gle = GetLastError();
2349 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2350 errorlevel = 1;
2351 } else if ((!status)) WCMD_print_error();
2355 /****************************************************************************
2356 * WCMD_setshow_path
2358 * Set/Show the path environment variable
2361 void WCMD_setshow_path (WCHAR *command) {
2363 WCHAR string[1024];
2364 DWORD status;
2365 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2366 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2368 if (strlenW(param1) == 0) {
2369 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2370 if (status != 0) {
2371 WCMD_output_asis ( pathEqW);
2372 WCMD_output_asis ( string);
2373 WCMD_output_asis ( newline);
2375 else {
2376 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2379 else {
2380 if (*command == '=') command++; /* Skip leading '=' */
2381 status = SetEnvironmentVariableW(pathW, command);
2382 if (!status) WCMD_print_error();
2386 /****************************************************************************
2387 * WCMD_setshow_prompt
2389 * Set or show the command prompt.
2392 void WCMD_setshow_prompt (void) {
2394 WCHAR *s;
2395 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2397 if (strlenW(param1) == 0) {
2398 SetEnvironmentVariableW(promptW, NULL);
2400 else {
2401 s = param1;
2402 while ((*s == '=') || (*s == ' ')) s++;
2403 if (strlenW(s) == 0) {
2404 SetEnvironmentVariableW(promptW, NULL);
2406 else SetEnvironmentVariableW(promptW, s);
2410 /****************************************************************************
2411 * WCMD_setshow_time
2413 * Set/Show the system time
2414 * FIXME: Can't change time yet
2417 void WCMD_setshow_time (void) {
2419 WCHAR curtime[64], buffer[64];
2420 DWORD count;
2421 SYSTEMTIME st;
2422 static const WCHAR parmT[] = {'/','T','\0'};
2424 if (strlenW(param1) == 0) {
2425 GetLocalTime(&st);
2426 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2427 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2428 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2429 if (strstrW (quals, parmT) == NULL) {
2430 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2431 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2432 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2433 if (count > 2) {
2434 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2438 else WCMD_print_error ();
2440 else {
2441 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2445 /****************************************************************************
2446 * WCMD_shift
2448 * Shift batch parameters.
2449 * Optional /n says where to start shifting (n=0-8)
2452 void WCMD_shift (WCHAR *command) {
2453 int start;
2455 if (context != NULL) {
2456 WCHAR *pos = strchrW(command, '/');
2457 int i;
2459 if (pos == NULL) {
2460 start = 0;
2461 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2462 start = (*(pos+1) - '0');
2463 } else {
2464 SetLastError(ERROR_INVALID_PARAMETER);
2465 WCMD_print_error();
2466 return;
2469 WINE_TRACE("Shifting variables, starting at %d\n", start);
2470 for (i=start;i<=8;i++) {
2471 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2473 context -> shift_count[9] = context -> shift_count[9] + 1;
2478 /****************************************************************************
2479 * WCMD_title
2481 * Set the console title
2483 void WCMD_title (WCHAR *command) {
2484 SetConsoleTitleW(command);
2487 /****************************************************************************
2488 * WCMD_type
2490 * Copy a file to standard output.
2493 void WCMD_type (WCHAR *command) {
2495 int argno = 0;
2496 WCHAR *argN = command;
2497 BOOL writeHeaders = FALSE;
2499 if (param1[0] == 0x00) {
2500 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2501 return;
2504 if (param2[0] != 0x00) writeHeaders = TRUE;
2506 /* Loop through all args */
2507 errorlevel = 0;
2508 while (argN) {
2509 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2511 HANDLE h;
2512 WCHAR buffer[512];
2513 DWORD count;
2515 if (!argN) break;
2517 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2518 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2519 FILE_ATTRIBUTE_NORMAL, NULL);
2520 if (h == INVALID_HANDLE_VALUE) {
2521 WCMD_print_error ();
2522 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2523 errorlevel = 1;
2524 } else {
2525 if (writeHeaders) {
2526 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2527 WCMD_output(fmt, thisArg);
2529 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2530 if (count == 0) break; /* ReadFile reports success on EOF! */
2531 buffer[count] = 0;
2532 WCMD_output_asis (buffer);
2534 CloseHandle (h);
2535 if (!writeHeaders)
2536 WCMD_output_asis (newline);
2541 /****************************************************************************
2542 * WCMD_more
2544 * Output either a file or stdin to screen in pages
2547 void WCMD_more (WCHAR *command) {
2549 int argno = 0;
2550 WCHAR *argN = command;
2551 WCHAR moreStr[100];
2552 WCHAR moreStrPage[100];
2553 WCHAR buffer[512];
2554 DWORD count;
2555 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2556 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2557 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2558 ')',' ','-','-','\n','\0'};
2559 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2561 /* Prefix the NLS more with '-- ', then load the text */
2562 errorlevel = 0;
2563 strcpyW(moreStr, moreStart);
2564 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2565 (sizeof(moreStr)/sizeof(WCHAR))-3);
2567 if (param1[0] == 0x00) {
2569 /* Wine implements pipes via temporary files, and hence stdin is
2570 effectively reading from the file. This means the prompts for
2571 more are satisfied by the next line from the input (file). To
2572 avoid this, ensure stdin is to the console */
2573 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2574 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2575 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2576 FILE_ATTRIBUTE_NORMAL, 0);
2577 WINE_TRACE("No parms - working probably in pipe mode\n");
2578 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2580 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2581 once you get in this bit unless due to a pipe, its going to end badly... */
2582 wsprintfW(moreStrPage, moreFmt, moreStr);
2584 WCMD_enter_paged_mode(moreStrPage);
2585 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2586 if (count == 0) break; /* ReadFile reports success on EOF! */
2587 buffer[count] = 0;
2588 WCMD_output_asis (buffer);
2590 WCMD_leave_paged_mode();
2592 /* Restore stdin to what it was */
2593 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2594 CloseHandle(hConIn);
2596 return;
2597 } else {
2598 BOOL needsPause = FALSE;
2600 /* Loop through all args */
2601 WINE_TRACE("Parms supplied - working through each file\n");
2602 WCMD_enter_paged_mode(moreStrPage);
2604 while (argN) {
2605 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2606 HANDLE h;
2608 if (!argN) break;
2610 if (needsPause) {
2612 /* Wait */
2613 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2614 WCMD_leave_paged_mode();
2615 WCMD_output_asis(moreStrPage);
2616 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2617 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2618 WCMD_enter_paged_mode(moreStrPage);
2622 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2623 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2624 FILE_ATTRIBUTE_NORMAL, NULL);
2625 if (h == INVALID_HANDLE_VALUE) {
2626 WCMD_print_error ();
2627 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2628 errorlevel = 1;
2629 } else {
2630 ULONG64 curPos = 0;
2631 ULONG64 fileLen = 0;
2632 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2634 /* Get the file size */
2635 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2636 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2638 needsPause = TRUE;
2639 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2640 if (count == 0) break; /* ReadFile reports success on EOF! */
2641 buffer[count] = 0;
2642 curPos += count;
2644 /* Update % count (would be used in WCMD_output_asis as prompt) */
2645 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2647 WCMD_output_asis (buffer);
2649 CloseHandle (h);
2653 WCMD_leave_paged_mode();
2657 /****************************************************************************
2658 * WCMD_verify
2660 * Display verify flag.
2661 * FIXME: We don't actually do anything with the verify flag other than toggle
2662 * it...
2665 void WCMD_verify (WCHAR *command) {
2667 int count;
2669 count = strlenW(command);
2670 if (count == 0) {
2671 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2672 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2673 return;
2675 if (lstrcmpiW(command, onW) == 0) {
2676 verify_mode = 1;
2677 return;
2679 else if (lstrcmpiW(command, offW) == 0) {
2680 verify_mode = 0;
2681 return;
2683 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2686 /****************************************************************************
2687 * WCMD_version
2689 * Display version info.
2692 void WCMD_version (void) {
2694 WCMD_output (version_string);
2698 /****************************************************************************
2699 * WCMD_volume
2701 * Display volume info and/or set volume label. Returns 0 if error.
2704 int WCMD_volume (int mode, WCHAR *path) {
2706 DWORD count, serial;
2707 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2708 BOOL status;
2710 if (strlenW(path) == 0) {
2711 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2712 if (!status) {
2713 WCMD_print_error ();
2714 return 0;
2716 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2717 &serial, NULL, NULL, NULL, 0);
2719 else {
2720 static const WCHAR fmt[] = {'%','s','\\','\0'};
2721 if ((path[1] != ':') || (strlenW(path) != 2)) {
2722 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2723 return 0;
2725 wsprintfW (curdir, fmt, path);
2726 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2727 &serial, NULL,
2728 NULL, NULL, 0);
2730 if (!status) {
2731 WCMD_print_error ();
2732 return 0;
2734 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2735 curdir[0], label, HIWORD(serial), LOWORD(serial));
2736 if (mode) {
2737 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2738 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2739 sizeof(string)/sizeof(WCHAR), &count, NULL);
2740 if (count > 1) {
2741 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2742 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2744 if (strlenW(path) != 0) {
2745 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2747 else {
2748 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2751 return 1;
2754 /**************************************************************************
2755 * WCMD_exit
2757 * Exit either the process, or just this batch program
2761 void WCMD_exit (CMD_LIST **cmdList) {
2763 static const WCHAR parmB[] = {'/','B','\0'};
2764 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2766 if (context && lstrcmpiW(quals, parmB) == 0) {
2767 errorlevel = rc;
2768 context -> skip_rest = TRUE;
2769 *cmdList = NULL;
2770 } else {
2771 ExitProcess(rc);
2776 /*****************************************************************************
2777 * WCMD_assoc
2779 * Lists or sets file associations (assoc = TRUE)
2780 * Lists or sets file types (assoc = FALSE)
2782 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2784 HKEY key;
2785 DWORD accessOptions = KEY_READ;
2786 WCHAR *newValue;
2787 LONG rc = ERROR_SUCCESS;
2788 WCHAR keyValue[MAXSTRING];
2789 DWORD valueLen = MAXSTRING;
2790 HKEY readKey;
2791 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2792 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2794 /* See if parameter includes '=' */
2795 errorlevel = 0;
2796 newValue = strchrW(command, '=');
2797 if (newValue) accessOptions |= KEY_WRITE;
2799 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2800 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2801 accessOptions, &key) != ERROR_SUCCESS) {
2802 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2803 return;
2806 /* If no parameters then list all associations */
2807 if (*command == 0x00) {
2808 int index = 0;
2810 /* Enumerate all the keys */
2811 while (rc != ERROR_NO_MORE_ITEMS) {
2812 WCHAR keyName[MAXSTRING];
2813 DWORD nameLen;
2815 /* Find the next value */
2816 nameLen = MAXSTRING;
2817 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2819 if (rc == ERROR_SUCCESS) {
2821 /* Only interested in extension ones if assoc, or others
2822 if not assoc */
2823 if ((keyName[0] == '.' && assoc) ||
2824 (!(keyName[0] == '.') && (!assoc)))
2826 WCHAR subkey[MAXSTRING];
2827 strcpyW(subkey, keyName);
2828 if (!assoc) strcatW(subkey, shOpCmdW);
2830 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2832 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2833 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2834 WCMD_output_asis(keyName);
2835 WCMD_output_asis(equalW);
2836 /* If no default value found, leave line empty after '=' */
2837 if (rc == ERROR_SUCCESS) {
2838 WCMD_output_asis(keyValue);
2840 WCMD_output_asis(newline);
2841 RegCloseKey(readKey);
2847 } else {
2849 /* Parameter supplied - if no '=' on command line, its a query */
2850 if (newValue == NULL) {
2851 WCHAR *space;
2852 WCHAR subkey[MAXSTRING];
2854 /* Query terminates the parameter at the first space */
2855 strcpyW(keyValue, command);
2856 space = strchrW(keyValue, ' ');
2857 if (space) *space=0x00;
2859 /* Set up key name */
2860 strcpyW(subkey, keyValue);
2861 if (!assoc) strcatW(subkey, shOpCmdW);
2863 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2865 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2866 WCMD_output_asis(command);
2867 WCMD_output_asis(equalW);
2868 /* If no default value found, leave line empty after '=' */
2869 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2870 WCMD_output_asis(newline);
2871 RegCloseKey(readKey);
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, sizeof(msgbuffer)/sizeof(WCHAR));
2880 } else {
2881 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2883 wsprintfW(outbuffer, msgbuffer, keyValue);
2884 WCMD_output_asis(outbuffer);
2885 errorlevel = 2;
2888 /* Not a query - its a set or clear of a value */
2889 } else {
2891 WCHAR subkey[MAXSTRING];
2893 /* Get pointer to new value */
2894 *newValue = 0x00;
2895 newValue++;
2897 /* Set up key name */
2898 strcpyW(subkey, command);
2899 if (!assoc) strcatW(subkey, shOpCmdW);
2901 /* If nothing after '=' then clear value - only valid for ASSOC */
2902 if (*newValue == 0x00) {
2904 if (assoc) rc = RegDeleteKeyW(key, command);
2905 if (assoc && rc == ERROR_SUCCESS) {
2906 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2908 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2909 WCMD_print_error();
2910 errorlevel = 2;
2912 } else {
2913 WCHAR msgbuffer[MAXSTRING];
2914 WCHAR outbuffer[MAXSTRING];
2916 /* Load the translated 'File association not found' */
2917 if (assoc) {
2918 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2919 sizeof(msgbuffer)/sizeof(WCHAR));
2920 } else {
2921 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2922 sizeof(msgbuffer)/sizeof(WCHAR));
2924 wsprintfW(outbuffer, msgbuffer, keyValue);
2925 WCMD_output_asis(outbuffer);
2926 errorlevel = 2;
2929 /* It really is a set value = contents */
2930 } else {
2931 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2932 accessOptions, NULL, &readKey, NULL);
2933 if (rc == ERROR_SUCCESS) {
2934 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2935 (LPBYTE)newValue, strlenW(newValue));
2936 RegCloseKey(readKey);
2939 if (rc != ERROR_SUCCESS) {
2940 WCMD_print_error();
2941 errorlevel = 2;
2942 } else {
2943 WCMD_output_asis(command);
2944 WCMD_output_asis(equalW);
2945 WCMD_output_asis(newValue);
2946 WCMD_output_asis(newline);
2952 /* Clean up */
2953 RegCloseKey(key);
2956 /****************************************************************************
2957 * WCMD_color
2959 * Clear the terminal screen.
2962 void WCMD_color (void) {
2964 /* Emulate by filling the screen from the top left to bottom right with
2965 spaces, then moving the cursor to the top left afterwards */
2966 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2967 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2969 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2970 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2971 return;
2974 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2976 COORD topLeft;
2977 DWORD screenSize;
2978 DWORD color = 0;
2980 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2982 topLeft.X = 0;
2983 topLeft.Y = 0;
2985 /* Convert the color hex digits */
2986 if (param1[0] == 0x00) {
2987 color = defaultColor;
2988 } else {
2989 color = strtoulW(param1, NULL, 16);
2992 /* Fail if fg == bg color */
2993 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2994 errorlevel = 1;
2995 return;
2998 /* Set the current screen contents and ensure all future writes
2999 remain this color */
3000 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3001 SetConsoleTextAttribute(hStdOut, color);