cmd: Add CHOICE builtin with DOS6 to XP commandline parameter.
[wine/multimedia.git] / programs / cmd / builtins.c
blob7c35e6ad6f923bdb239d78c012a657227a42422b
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 /****************************************************************************
536 * WCMD_delete
538 * Delete a file or wildcarded set.
540 * Note on /A:
541 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
542 * - Each set is a pattern, eg /ahr /as-r means
543 * readonly+hidden OR nonreadonly system files
544 * - The '-' applies to a single field, ie /a:-hr means read only
545 * non-hidden files
548 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
550 int argno = 0;
551 int argsProcessed = 0;
552 WCHAR *argN = command;
553 BOOL foundAny = FALSE;
554 static const WCHAR parmA[] = {'/','A','\0'};
555 static const WCHAR parmQ[] = {'/','Q','\0'};
556 static const WCHAR parmP[] = {'/','P','\0'};
557 static const WCHAR parmS[] = {'/','S','\0'};
558 static const WCHAR parmF[] = {'/','F','\0'};
560 /* If not recursing, clear error flag */
561 if (expectDir) errorlevel = 0;
563 /* Loop through all args */
564 while (argN) {
565 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
566 WCHAR argCopy[MAX_PATH];
568 if (argN && argN[0] != '/') {
570 WIN32_FIND_DATAW fd;
571 HANDLE hff;
572 WCHAR fpath[MAX_PATH];
573 WCHAR *p;
574 BOOL handleParm = TRUE;
575 BOOL found = FALSE;
576 static const WCHAR anyExt[]= {'.','*','\0'};
578 strcpyW(argCopy, thisArg);
579 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
580 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
581 argsProcessed++;
583 /* If filename part of parameter is * or *.*, prompt unless
584 /Q supplied. */
585 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
587 WCHAR drive[10];
588 WCHAR dir[MAX_PATH];
589 WCHAR fname[MAX_PATH];
590 WCHAR ext[MAX_PATH];
592 /* Convert path into actual directory spec */
593 GetFullPathNameW(argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
594 WCMD_splitpath(fpath, drive, dir, fname, ext);
596 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
597 if ((strcmpW(fname, starW) == 0) &&
598 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
599 BOOL ok;
600 WCHAR question[MAXSTRING];
601 static const WCHAR fmt[] = {'%','s',' ','\0'};
603 /* Note: Flag as found, to avoid file not found message */
604 found = TRUE;
606 /* Ask for confirmation */
607 wsprintfW(question, fmt, fpath);
608 ok = WCMD_ask_confirm(question, TRUE, NULL);
610 /* Abort if answer is 'N' */
611 if (!ok) continue;
615 /* First, try to delete in the current directory */
616 hff = FindFirstFileW(argCopy, &fd);
617 if (hff == INVALID_HANDLE_VALUE) {
618 handleParm = FALSE;
619 } else {
620 found = TRUE;
623 /* Support del <dirname> by just deleting all files dirname\* */
624 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
625 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
626 WCHAR modifiedParm[MAX_PATH];
627 static const WCHAR slashStar[] = {'\\','*','\0'};
629 strcpyW(modifiedParm, argCopy);
630 strcatW(modifiedParm, slashStar);
631 FindClose(hff);
632 found = TRUE;
633 WCMD_delete(modifiedParm, FALSE);
635 } else if (handleParm) {
637 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
638 strcpyW (fpath, argCopy);
639 do {
640 p = strrchrW (fpath, '\\');
641 if (p != NULL) {
642 *++p = '\0';
643 strcatW (fpath, fd.cFileName);
645 else strcpyW (fpath, fd.cFileName);
646 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
647 BOOL ok = TRUE;
648 WCHAR *nextA = strstrW (quals, parmA);
650 /* Handle attribute matching (/A) */
651 if (nextA != NULL) {
652 ok = FALSE;
653 while (nextA != NULL && !ok) {
655 WCHAR *thisA = (nextA+2);
656 BOOL stillOK = TRUE;
658 /* Skip optional : */
659 if (*thisA == ':') thisA++;
661 /* Parse each of the /A[:]xxx in turn */
662 while (*thisA && *thisA != '/') {
663 BOOL negate = FALSE;
664 BOOL attribute = FALSE;
666 /* Match negation of attribute first */
667 if (*thisA == '-') {
668 negate=TRUE;
669 thisA++;
672 /* Match attribute */
673 switch (*thisA) {
674 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
675 break;
676 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
677 break;
678 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
679 break;
680 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
681 break;
682 default:
683 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
686 /* Now check result, keeping a running boolean about whether it
687 matches all parsed attributes so far */
688 if (attribute && !negate) {
689 stillOK = stillOK;
690 } else if (!attribute && negate) {
691 stillOK = stillOK;
692 } else {
693 stillOK = FALSE;
695 thisA++;
698 /* Save the running total as the final result */
699 ok = stillOK;
701 /* Step on to next /A set */
702 nextA = strstrW (nextA+1, parmA);
706 /* /P means prompt for each file */
707 if (ok && strstrW (quals, parmP) != NULL) {
708 WCHAR question[MAXSTRING];
710 /* Ask for confirmation */
711 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
712 ok = WCMD_ask_confirm(question, FALSE, NULL);
715 /* Only proceed if ok to */
716 if (ok) {
718 /* If file is read only, and /F supplied, delete it */
719 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
720 strstrW (quals, parmF) != NULL) {
721 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
724 /* Now do the delete */
725 if (!DeleteFileW(fpath)) WCMD_print_error ();
729 } while (FindNextFileW(hff, &fd) != 0);
730 FindClose (hff);
733 /* Now recurse into all subdirectories handling the parameter in the same way */
734 if (strstrW (quals, parmS) != NULL) {
736 WCHAR thisDir[MAX_PATH];
737 int cPos;
739 WCHAR drive[10];
740 WCHAR dir[MAX_PATH];
741 WCHAR fname[MAX_PATH];
742 WCHAR ext[MAX_PATH];
744 /* Convert path into actual directory spec */
745 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
746 WCMD_splitpath(thisDir, drive, dir, fname, ext);
748 strcpyW(thisDir, drive);
749 strcatW(thisDir, dir);
750 cPos = strlenW(thisDir);
752 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
754 /* Append '*' to the directory */
755 thisDir[cPos] = '*';
756 thisDir[cPos+1] = 0x00;
758 hff = FindFirstFileW(thisDir, &fd);
760 /* Remove residual '*' */
761 thisDir[cPos] = 0x00;
763 if (hff != INVALID_HANDLE_VALUE) {
764 DIRECTORY_STACK *allDirs = NULL;
765 DIRECTORY_STACK *lastEntry = NULL;
767 do {
768 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
769 (strcmpW(fd.cFileName, dotdotW) != 0) &&
770 (strcmpW(fd.cFileName, dotW) != 0)) {
772 DIRECTORY_STACK *nextDir;
773 WCHAR subParm[MAX_PATH];
775 /* Work out search parameter in sub dir */
776 strcpyW (subParm, thisDir);
777 strcatW (subParm, fd.cFileName);
778 strcatW (subParm, slashW);
779 strcatW (subParm, fname);
780 strcatW (subParm, ext);
781 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
783 /* Allocate memory, add to list */
784 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
785 if (allDirs == NULL) allDirs = nextDir;
786 if (lastEntry != NULL) lastEntry->next = nextDir;
787 lastEntry = nextDir;
788 nextDir->next = NULL;
789 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
790 (strlenW(subParm)+1) * sizeof(WCHAR));
791 strcpyW(nextDir->dirName, subParm);
793 } while (FindNextFileW(hff, &fd) != 0);
794 FindClose (hff);
796 /* Go through each subdir doing the delete */
797 while (allDirs != NULL) {
798 DIRECTORY_STACK *tempDir;
800 tempDir = allDirs->next;
801 found |= WCMD_delete (allDirs->dirName, FALSE);
803 HeapFree(GetProcessHeap(),0,allDirs->dirName);
804 HeapFree(GetProcessHeap(),0,allDirs);
805 allDirs = tempDir;
809 /* Keep running total to see if any found, and if not recursing
810 issue error message */
811 if (expectDir) {
812 if (!found) {
813 errorlevel = 1;
814 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
817 foundAny |= found;
821 /* Handle no valid args */
822 if (argsProcessed == 0) {
823 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
826 return foundAny;
829 /****************************************************************************
830 * WCMD_echo
832 * Echo input to the screen (or not). We don't try to emulate the bugs
833 * in DOS (try typing "ECHO ON AGAIN" for an example).
836 void WCMD_echo (const WCHAR *command) {
838 int count;
839 const WCHAR *origcommand = command;
841 if (command[0]==' ' || command[0]=='.')
842 command++;
843 count = strlenW(command);
844 if (count == 0 && origcommand[0]!='.') {
845 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
846 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
847 return;
849 if (lstrcmpiW(command, onW) == 0) {
850 echo_mode = 1;
851 return;
853 if (lstrcmpiW(command, offW) == 0) {
854 echo_mode = 0;
855 return;
857 WCMD_output_asis (command);
858 WCMD_output (newline);
862 /**************************************************************************
863 * WCMD_for
865 * Batch file loop processing.
867 * On entry: cmdList contains the syntax up to the set
868 * next cmdList and all in that bracket contain the set data
869 * next cmdlist contains the DO cmd
870 * following that is either brackets or && entries (as per if)
874 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
876 WIN32_FIND_DATAW fd;
877 HANDLE hff;
878 int i;
879 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
880 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
881 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
882 WCHAR variable[4];
883 WCHAR *firstCmd;
884 int thisDepth;
886 WCHAR *curPos = p;
887 BOOL expandDirs = FALSE;
888 BOOL useNumbers = FALSE;
889 BOOL doFileset = FALSE;
890 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
891 int itemNum;
892 CMD_LIST *thisCmdStart;
895 /* Handle optional qualifiers (multiple are allowed) */
896 while (*curPos && *curPos == '/') {
897 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
898 curPos++;
899 switch (toupperW(*curPos)) {
900 case 'D': curPos++; expandDirs = TRUE; break;
901 case 'L': curPos++; useNumbers = TRUE; break;
903 /* Recursive is special case - /R can have an optional path following it */
904 /* filenamesets are another special case - /F can have an optional options following it */
905 case 'R':
906 case 'F':
908 BOOL isRecursive = (*curPos == 'R');
910 if (!isRecursive)
911 doFileset = TRUE;
913 /* Skip whitespace */
914 curPos++;
915 while (*curPos && *curPos==' ') curPos++;
917 /* Next parm is either qualifier, path/options or variable -
918 only care about it if it is the path/options */
919 if (*curPos && *curPos != '/' && *curPos != '%') {
920 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
921 else WINE_FIXME("/F needs to handle options\n");
923 break;
925 default:
926 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
927 curPos++;
930 /* Skip whitespace between qualifiers */
931 while (*curPos && *curPos==' ') curPos++;
934 /* Skip whitespace before variable */
935 while (*curPos && *curPos==' ') curPos++;
937 /* Ensure line continues with variable */
938 if (!*curPos || *curPos != '%') {
939 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
940 return;
943 /* Variable should follow */
944 i = 0;
945 while (curPos[i] && curPos[i]!=' ') i++;
946 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
947 variable[i] = 0x00;
948 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
949 curPos = &curPos[i];
951 /* Skip whitespace before IN */
952 while (*curPos && *curPos==' ') curPos++;
954 /* Ensure line continues with IN */
955 if (!*curPos || lstrcmpiW (curPos, inW)) {
956 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
957 return;
960 /* Save away where the set of data starts and the variable */
961 thisDepth = (*cmdList)->bracketDepth;
962 *cmdList = (*cmdList)->nextcommand;
963 setStart = (*cmdList);
965 /* Skip until the close bracket */
966 WINE_TRACE("Searching %p as the set\n", *cmdList);
967 while (*cmdList &&
968 (*cmdList)->command != NULL &&
969 (*cmdList)->bracketDepth > thisDepth) {
970 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
971 *cmdList = (*cmdList)->nextcommand;
974 /* Skip the close bracket, if there is one */
975 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
977 /* Syntax error if missing close bracket, or nothing following it
978 and once we have the complete set, we expect a DO */
979 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
980 if ((*cmdList == NULL) ||
981 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
982 (*cmdList)->command, 3, doW, -1) != 2)) {
983 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
984 return;
987 /* Save away the starting position for the commands (and offset for the
988 first one */
989 cmdStart = *cmdList;
990 cmdEnd = *cmdList;
991 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
992 itemNum = 0;
994 thisSet = setStart;
995 /* Loop through all set entries */
996 while (thisSet &&
997 thisSet->command != NULL &&
998 thisSet->bracketDepth >= thisDepth) {
1000 /* Loop through all entries on the same line */
1001 WCHAR *item;
1002 WCHAR *itemStart;
1004 WINE_TRACE("Processing for set %p\n", thisSet);
1005 i = 0;
1006 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
1009 * If the parameter within the set has a wildcard then search for matching files
1010 * otherwise do a literal substitution.
1012 static const WCHAR wildcards[] = {'*','?','\0'};
1013 thisCmdStart = cmdStart;
1015 itemNum++;
1016 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1018 if (!useNumbers && !doFileset) {
1019 if (strpbrkW (item, wildcards)) {
1020 hff = FindFirstFileW(item, &fd);
1021 if (hff != INVALID_HANDLE_VALUE) {
1022 do {
1023 BOOL isDirectory = FALSE;
1025 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1027 /* Handle as files or dirs appropriately, but ignore . and .. */
1028 if (isDirectory == expandDirs &&
1029 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1030 (strcmpW(fd.cFileName, dotW) != 0))
1032 thisCmdStart = cmdStart;
1033 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1034 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1035 fd.cFileName, FALSE, TRUE);
1038 } while (FindNextFileW(hff, &fd) != 0);
1039 FindClose (hff);
1041 } else {
1042 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1045 } else if (useNumbers) {
1046 /* Convert the first 3 numbers to signed longs and save */
1047 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1048 /* else ignore them! */
1050 /* Filesets - either a list of files, or a command to run and parse the output */
1051 } else if (doFileset && *itemStart != '"') {
1053 HANDLE input;
1054 WCHAR temp_file[MAX_PATH];
1056 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1057 wine_dbgstr_w(item));
1059 /* If backquote or single quote, we need to launch that command
1060 and parse the results - use a temporary file */
1061 if (*itemStart == '`' || *itemStart == '\'') {
1063 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1064 static const WCHAR redirOut[] = {'>','%','s','\0'};
1065 static const WCHAR cmdW[] = {'C','M','D','\0'};
1067 /* Remove trailing character */
1068 itemStart[strlenW(itemStart)-1] = 0x00;
1070 /* Get temp filename */
1071 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1072 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1074 /* Execute program and redirect output */
1075 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1076 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1078 /* Open the file, read line by line and process */
1079 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1080 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1081 } else {
1083 /* Open the file, read line by line and process */
1084 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1085 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1088 /* Process the input file */
1089 if (input == INVALID_HANDLE_VALUE) {
1090 WCMD_print_error ();
1091 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1092 errorlevel = 1;
1093 return; /* FOR loop aborts at first failure here */
1095 } else {
1097 WCHAR buffer[MAXSTRING] = {'\0'};
1098 WCHAR *where, *parm;
1100 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1102 /* Skip blank lines*/
1103 parm = WCMD_parameter (buffer, 0, &where);
1104 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1105 wine_dbgstr_w(buffer));
1107 if (where) {
1108 /* FIXME: The following should be moved into its own routine and
1109 reused for the string literal parsing below */
1110 thisCmdStart = cmdStart;
1111 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1112 cmdEnd = thisCmdStart;
1115 buffer[0] = 0x00;
1118 CloseHandle (input);
1121 /* Delete the temporary file */
1122 if (*itemStart == '`' || *itemStart == '\'') {
1123 DeleteFileW(temp_file);
1126 /* Filesets - A string literal */
1127 } else if (doFileset && *itemStart == '"') {
1128 WCHAR buffer[MAXSTRING] = {'\0'};
1129 WCHAR *where, *parm;
1131 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1132 strcpyW(buffer, item);
1133 parm = WCMD_parameter (buffer, 0, &where);
1134 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1135 wine_dbgstr_w(buffer));
1137 if (where) {
1138 /* FIXME: The following should be moved into its own routine and
1139 reused for the string literal parsing below */
1140 thisCmdStart = cmdStart;
1141 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1142 cmdEnd = thisCmdStart;
1146 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1147 cmdEnd = thisCmdStart;
1148 i++;
1151 /* Move onto the next set line */
1152 thisSet = thisSet->nextcommand;
1155 /* If /L is provided, now run the for loop */
1156 if (useNumbers) {
1157 WCHAR thisNum[20];
1158 static const WCHAR fmt[] = {'%','d','\0'};
1160 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1161 numbers[0], numbers[2], numbers[1]);
1162 for (i=numbers[0];
1163 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1164 i=i + numbers[1]) {
1166 sprintfW(thisNum, fmt, i);
1167 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1169 thisCmdStart = cmdStart;
1170 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1171 cmdEnd = thisCmdStart;
1175 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1176 all processing, OR it should be pointing to the end of && processing OR
1177 it should be pointing at the NULL end of bracket for the DO. The return
1178 value needs to be the NEXT command to execute, which it either is, or
1179 we need to step over the closing bracket */
1180 *cmdList = cmdEnd;
1181 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1185 /*****************************************************************************
1186 * WCMD_part_execute
1188 * Execute a command, and any && or bracketed follow on to the command. The
1189 * first command to be executed may not be at the front of the
1190 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1192 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1193 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1195 CMD_LIST *curPosition = *cmdList;
1196 int myDepth = (*cmdList)->bracketDepth;
1198 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1199 cmdList, wine_dbgstr_w(firstcmd),
1200 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1201 conditionTRUE);
1203 /* Skip leading whitespace between condition and the command */
1204 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1206 /* Process the first command, if there is one */
1207 if (conditionTRUE && firstcmd && *firstcmd) {
1208 WCHAR *command = WCMD_strdupW(firstcmd);
1209 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1210 HeapFree(GetProcessHeap(), 0, command);
1214 /* If it didn't move the position, step to next command */
1215 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1217 /* Process any other parts of the command */
1218 if (*cmdList) {
1219 BOOL processThese = TRUE;
1221 if (isIF) processThese = conditionTRUE;
1223 while (*cmdList) {
1224 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1226 /* execute all appropriate commands */
1227 curPosition = *cmdList;
1229 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1230 *cmdList,
1231 (*cmdList)->prevDelim,
1232 (*cmdList)->bracketDepth, myDepth);
1234 /* Execute any statements appended to the line */
1235 /* FIXME: Only if previous call worked for && or failed for || */
1236 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1237 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1238 if (processThese && (*cmdList)->command) {
1239 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1240 value, cmdList);
1242 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1244 /* Execute any appended to the statement with (...) */
1245 } else if ((*cmdList)->bracketDepth > myDepth) {
1246 if (processThese) {
1247 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1248 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1250 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1252 /* End of the command - does 'ELSE ' follow as the next command? */
1253 } else {
1254 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1255 NORM_IGNORECASE | SORT_STRINGSORT,
1256 (*cmdList)->command, 5, ifElse, -1) == 2) {
1258 /* Swap between if and else processing */
1259 processThese = !processThese;
1261 /* Process the ELSE part */
1262 if (processThese) {
1263 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1265 /* Skip leading whitespace between condition and the command */
1266 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1267 if (*cmd) {
1268 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1271 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1272 } else {
1273 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1274 break;
1279 return;
1282 /**************************************************************************
1283 * WCMD_give_help
1285 * Simple on-line help. Help text is stored in the resource file.
1288 void WCMD_give_help (WCHAR *command) {
1290 int i;
1292 command = WCMD_strtrim_leading_spaces(command);
1293 if (strlenW(command) == 0) {
1294 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1296 else {
1297 for (i=0; i<=WCMD_EXIT; i++) {
1298 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1299 command, -1, inbuilt[i], -1) == 2) {
1300 WCMD_output_asis (WCMD_LoadMessage(i));
1301 return;
1304 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1306 return;
1309 /****************************************************************************
1310 * WCMD_go_to
1312 * Batch file jump instruction. Not the most efficient algorithm ;-)
1313 * Prints error message if the specified label cannot be found - the file pointer is
1314 * then at EOF, effectively stopping the batch file.
1315 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1318 void WCMD_goto (CMD_LIST **cmdList) {
1320 WCHAR string[MAX_PATH];
1321 WCHAR current[MAX_PATH];
1323 /* Do not process any more parts of a processed multipart or multilines command */
1324 if (cmdList) *cmdList = NULL;
1326 if (param1[0] == 0x00) {
1327 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1328 return;
1330 if (context != NULL) {
1331 WCHAR *paramStart = param1, *str;
1332 static const WCHAR eofW[] = {':','e','o','f','\0'};
1334 /* Handle special :EOF label */
1335 if (lstrcmpiW (eofW, param1) == 0) {
1336 context -> skip_rest = TRUE;
1337 return;
1340 /* Support goto :label as well as goto label */
1341 if (*paramStart == ':') paramStart++;
1343 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1344 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1345 str = string;
1346 while (isspaceW (*str)) str++;
1347 if (*str == ':') {
1348 DWORD index = 0;
1349 str++;
1350 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1351 index++;
1353 /* ignore space at the end */
1354 current[index] = 0;
1355 if (lstrcmpiW (current, paramStart) == 0) return;
1358 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1360 return;
1363 /*****************************************************************************
1364 * WCMD_pushd
1366 * Push a directory onto the stack
1369 void WCMD_pushd (WCHAR *command) {
1370 struct env_stack *curdir;
1371 WCHAR *thisdir;
1372 static const WCHAR parmD[] = {'/','D','\0'};
1374 if (strchrW(command, '/') != NULL) {
1375 SetLastError(ERROR_INVALID_PARAMETER);
1376 WCMD_print_error();
1377 return;
1380 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1381 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1382 if( !curdir || !thisdir ) {
1383 LocalFree(curdir);
1384 LocalFree(thisdir);
1385 WINE_ERR ("out of memory\n");
1386 return;
1389 /* Change directory using CD code with /D parameter */
1390 strcpyW(quals, parmD);
1391 GetCurrentDirectoryW (1024, thisdir);
1392 errorlevel = 0;
1393 WCMD_setshow_default(command);
1394 if (errorlevel) {
1395 LocalFree(curdir);
1396 LocalFree(thisdir);
1397 return;
1398 } else {
1399 curdir -> next = pushd_directories;
1400 curdir -> strings = thisdir;
1401 if (pushd_directories == NULL) {
1402 curdir -> u.stackdepth = 1;
1403 } else {
1404 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1406 pushd_directories = curdir;
1411 /*****************************************************************************
1412 * WCMD_popd
1414 * Pop a directory from the stack
1417 void WCMD_popd (void) {
1418 struct env_stack *temp = pushd_directories;
1420 if (!pushd_directories)
1421 return;
1423 /* pop the old environment from the stack, and make it the current dir */
1424 pushd_directories = temp->next;
1425 SetCurrentDirectoryW(temp->strings);
1426 LocalFree (temp->strings);
1427 LocalFree (temp);
1430 /****************************************************************************
1431 * WCMD_if
1433 * Batch file conditional.
1435 * On entry, cmdlist will point to command containing the IF, and optionally
1436 * the first command to execute (if brackets not found)
1437 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1438 * If ('s were found, execute all within that bracket
1439 * Command may optionally be followed by an ELSE - need to skip instructions
1440 * in the else using the same logic
1442 * FIXME: Much more syntax checking needed!
1445 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1447 int negate = 0, test = 0;
1448 WCHAR condition[MAX_PATH], *command, *s;
1449 static const WCHAR notW[] = {'n','o','t','\0'};
1450 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1451 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1452 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1453 static const WCHAR eqeqW[] = {'=','=','\0'};
1454 static const WCHAR parmI[] = {'/','I','\0'};
1456 if (!lstrcmpiW (param1, notW)) {
1457 negate = 1;
1458 strcpyW (condition, param2);
1460 else {
1461 strcpyW (condition, param1);
1463 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1465 if (!lstrcmpiW (condition, errlvlW)) {
1466 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1467 WCMD_parameter (p, 2+negate, &command);
1469 else if (!lstrcmpiW (condition, existW)) {
1470 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1471 test = 1;
1473 WCMD_parameter (p, 2+negate, &command);
1475 else if (!lstrcmpiW (condition, defdW)) {
1476 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1477 test = 1;
1479 WCMD_parameter (p, 2+negate, &command);
1481 else if ((s = strstrW (p, eqeqW))) {
1482 s += 2;
1483 if (strstrW (quals, parmI) == NULL) {
1484 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1486 else {
1487 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1489 WCMD_parameter (s, 1, &command);
1491 else {
1492 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1493 return;
1496 /* Process rest of IF statement which is on the same line
1497 Note: This may process all or some of the cmdList (eg a GOTO) */
1498 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1501 /****************************************************************************
1502 * WCMD_move
1504 * Move a file, directory tree or wildcarded set of files.
1507 void WCMD_move (void) {
1509 int status;
1510 WIN32_FIND_DATAW fd;
1511 HANDLE hff;
1512 WCHAR input[MAX_PATH];
1513 WCHAR output[MAX_PATH];
1514 WCHAR drive[10];
1515 WCHAR dir[MAX_PATH];
1516 WCHAR fname[MAX_PATH];
1517 WCHAR ext[MAX_PATH];
1519 if (param1[0] == 0x00) {
1520 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1521 return;
1524 /* If no destination supplied, assume current directory */
1525 if (param2[0] == 0x00) {
1526 strcpyW(param2, dotW);
1529 /* If 2nd parm is directory, then use original filename */
1530 /* Convert partial path to full path */
1531 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1532 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1533 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1534 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1536 /* Split into components */
1537 WCMD_splitpath(input, drive, dir, fname, ext);
1539 hff = FindFirstFileW(input, &fd);
1540 while (hff != INVALID_HANDLE_VALUE) {
1541 WCHAR dest[MAX_PATH];
1542 WCHAR src[MAX_PATH];
1543 DWORD attribs;
1545 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1547 /* Build src & dest name */
1548 strcpyW(src, drive);
1549 strcatW(src, dir);
1551 /* See if dest is an existing directory */
1552 attribs = GetFileAttributesW(output);
1553 if (attribs != INVALID_FILE_ATTRIBUTES &&
1554 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1555 strcpyW(dest, output);
1556 strcatW(dest, slashW);
1557 strcatW(dest, fd.cFileName);
1558 } else {
1559 strcpyW(dest, output);
1562 strcatW(src, fd.cFileName);
1564 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1565 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1567 /* Check if file is read only, otherwise move it */
1568 attribs = GetFileAttributesW(src);
1569 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1570 (attribs & FILE_ATTRIBUTE_READONLY)) {
1571 SetLastError(ERROR_ACCESS_DENIED);
1572 status = 0;
1573 } else {
1574 BOOL ok = TRUE;
1576 /* If destination exists, prompt unless /Y supplied */
1577 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1578 BOOL force = FALSE;
1579 WCHAR copycmd[MAXSTRING];
1580 int len;
1582 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1583 if (strstrW (quals, parmNoY))
1584 force = FALSE;
1585 else if (strstrW (quals, parmY))
1586 force = TRUE;
1587 else {
1588 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1589 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1590 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1591 && ! lstrcmpiW (copycmd, parmY));
1594 /* Prompt if overwriting */
1595 if (!force) {
1596 WCHAR question[MAXSTRING];
1597 WCHAR yesChar[10];
1599 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1601 /* Ask for confirmation */
1602 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1603 ok = WCMD_ask_confirm(question, FALSE, NULL);
1605 /* So delete the destination prior to the move */
1606 if (ok) {
1607 if (!DeleteFileW(dest)) {
1608 WCMD_print_error ();
1609 errorlevel = 1;
1610 ok = FALSE;
1616 if (ok) {
1617 status = MoveFileW(src, dest);
1618 } else {
1619 status = 1; /* Anything other than 0 to prevent error msg below */
1623 if (!status) {
1624 WCMD_print_error ();
1625 errorlevel = 1;
1628 /* Step on to next match */
1629 if (FindNextFileW(hff, &fd) == 0) {
1630 FindClose(hff);
1631 hff = INVALID_HANDLE_VALUE;
1632 break;
1637 /****************************************************************************
1638 * WCMD_pause
1640 * Wait for keyboard input.
1643 void WCMD_pause (void) {
1645 DWORD count;
1646 WCHAR string[32];
1648 WCMD_output (anykey);
1649 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1650 sizeof(string)/sizeof(WCHAR), &count, NULL);
1653 /****************************************************************************
1654 * WCMD_remove_dir
1656 * Delete a directory.
1659 void WCMD_remove_dir (WCHAR *command) {
1661 int argno = 0;
1662 int argsProcessed = 0;
1663 WCHAR *argN = command;
1664 static const WCHAR parmS[] = {'/','S','\0'};
1665 static const WCHAR parmQ[] = {'/','Q','\0'};
1667 /* Loop through all args */
1668 while (argN) {
1669 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1670 if (argN && argN[0] != '/') {
1671 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1672 wine_dbgstr_w(quals));
1673 argsProcessed++;
1675 /* If subdirectory search not supplied, just try to remove
1676 and report error if it fails (eg if it contains a file) */
1677 if (strstrW (quals, parmS) == NULL) {
1678 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1680 /* Otherwise use ShFileOp to recursively remove a directory */
1681 } else {
1683 SHFILEOPSTRUCTW lpDir;
1685 /* Ask first */
1686 if (strstrW (quals, parmQ) == NULL) {
1687 BOOL ok;
1688 WCHAR question[MAXSTRING];
1689 static const WCHAR fmt[] = {'%','s',' ','\0'};
1691 /* Ask for confirmation */
1692 wsprintfW(question, fmt, thisArg);
1693 ok = WCMD_ask_confirm(question, TRUE, NULL);
1695 /* Abort if answer is 'N' */
1696 if (!ok) return;
1699 /* Do the delete */
1700 lpDir.hwnd = NULL;
1701 lpDir.pTo = NULL;
1702 lpDir.pFrom = thisArg;
1703 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1704 lpDir.wFunc = FO_DELETE;
1705 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1710 /* Handle no valid args */
1711 if (argsProcessed == 0) {
1712 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1713 return;
1718 /****************************************************************************
1719 * WCMD_rename
1721 * Rename a file.
1724 void WCMD_rename (void) {
1726 int status;
1727 HANDLE hff;
1728 WIN32_FIND_DATAW fd;
1729 WCHAR input[MAX_PATH];
1730 WCHAR *dotDst = NULL;
1731 WCHAR drive[10];
1732 WCHAR dir[MAX_PATH];
1733 WCHAR fname[MAX_PATH];
1734 WCHAR ext[MAX_PATH];
1735 DWORD attribs;
1737 errorlevel = 0;
1739 /* Must be at least two args */
1740 if (param1[0] == 0x00 || param2[0] == 0x00) {
1741 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1742 errorlevel = 1;
1743 return;
1746 /* Destination cannot contain a drive letter or directory separator */
1747 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1748 SetLastError(ERROR_INVALID_PARAMETER);
1749 WCMD_print_error();
1750 errorlevel = 1;
1751 return;
1754 /* Convert partial path to full path */
1755 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1756 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1757 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1758 dotDst = strchrW(param2, '.');
1760 /* Split into components */
1761 WCMD_splitpath(input, drive, dir, fname, ext);
1763 hff = FindFirstFileW(input, &fd);
1764 while (hff != INVALID_HANDLE_VALUE) {
1765 WCHAR dest[MAX_PATH];
1766 WCHAR src[MAX_PATH];
1767 WCHAR *dotSrc = NULL;
1768 int dirLen;
1770 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1772 /* FIXME: If dest name or extension is *, replace with filename/ext
1773 part otherwise use supplied name. This supports:
1774 ren *.fred *.jim
1775 ren jim.* fred.* etc
1776 However, windows has a more complex algorithm supporting eg
1777 ?'s and *'s mid name */
1778 dotSrc = strchrW(fd.cFileName, '.');
1780 /* Build src & dest name */
1781 strcpyW(src, drive);
1782 strcatW(src, dir);
1783 strcpyW(dest, src);
1784 dirLen = strlenW(src);
1785 strcatW(src, fd.cFileName);
1787 /* Build name */
1788 if (param2[0] == '*') {
1789 strcatW(dest, fd.cFileName);
1790 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1791 } else {
1792 strcatW(dest, param2);
1793 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1796 /* Build Extension */
1797 if (dotDst && (*(dotDst+1)=='*')) {
1798 if (dotSrc) strcatW(dest, dotSrc);
1799 } else if (dotDst) {
1800 if (dotDst) strcatW(dest, dotDst);
1803 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1804 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1806 /* Check if file is read only, otherwise move it */
1807 attribs = GetFileAttributesW(src);
1808 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1809 (attribs & FILE_ATTRIBUTE_READONLY)) {
1810 SetLastError(ERROR_ACCESS_DENIED);
1811 status = 0;
1812 } else {
1813 status = MoveFileW(src, dest);
1816 if (!status) {
1817 WCMD_print_error ();
1818 errorlevel = 1;
1821 /* Step on to next match */
1822 if (FindNextFileW(hff, &fd) == 0) {
1823 FindClose(hff);
1824 hff = INVALID_HANDLE_VALUE;
1825 break;
1830 /*****************************************************************************
1831 * WCMD_dupenv
1833 * Make a copy of the environment.
1835 static WCHAR *WCMD_dupenv( const WCHAR *env )
1837 WCHAR *env_copy;
1838 int len;
1840 if( !env )
1841 return NULL;
1843 len = 0;
1844 while ( env[len] )
1845 len += (strlenW(&env[len]) + 1);
1847 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1848 if (!env_copy)
1850 WINE_ERR("out of memory\n");
1851 return env_copy;
1853 memcpy (env_copy, env, len*sizeof (WCHAR));
1854 env_copy[len] = 0;
1856 return env_copy;
1859 /*****************************************************************************
1860 * WCMD_setlocal
1862 * setlocal pushes the environment onto a stack
1863 * Save the environment as unicode so we don't screw anything up.
1865 void WCMD_setlocal (const WCHAR *s) {
1866 WCHAR *env;
1867 struct env_stack *env_copy;
1868 WCHAR cwd[MAX_PATH];
1870 /* DISABLEEXTENSIONS ignored */
1872 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1873 if( !env_copy )
1875 WINE_ERR ("out of memory\n");
1876 return;
1879 env = GetEnvironmentStringsW ();
1881 env_copy->strings = WCMD_dupenv (env);
1882 if (env_copy->strings)
1884 env_copy->next = saved_environment;
1885 saved_environment = env_copy;
1887 /* Save the current drive letter */
1888 GetCurrentDirectoryW(MAX_PATH, cwd);
1889 env_copy->u.cwd = cwd[0];
1891 else
1892 LocalFree (env_copy);
1894 FreeEnvironmentStringsW (env);
1898 /*****************************************************************************
1899 * WCMD_endlocal
1901 * endlocal pops the environment off a stack
1902 * Note: When searching for '=', search from WCHAR position 1, to handle
1903 * special internal environment variables =C:, =D: etc
1905 void WCMD_endlocal (void) {
1906 WCHAR *env, *old, *p;
1907 struct env_stack *temp;
1908 int len, n;
1910 if (!saved_environment)
1911 return;
1913 /* pop the old environment from the stack */
1914 temp = saved_environment;
1915 saved_environment = temp->next;
1917 /* delete the current environment, totally */
1918 env = GetEnvironmentStringsW ();
1919 old = WCMD_dupenv (GetEnvironmentStringsW ());
1920 len = 0;
1921 while (old[len]) {
1922 n = strlenW(&old[len]) + 1;
1923 p = strchrW(&old[len] + 1, '=');
1924 if (p)
1926 *p++ = 0;
1927 SetEnvironmentVariableW (&old[len], NULL);
1929 len += n;
1931 LocalFree (old);
1932 FreeEnvironmentStringsW (env);
1934 /* restore old environment */
1935 env = temp->strings;
1936 len = 0;
1937 while (env[len]) {
1938 n = strlenW(&env[len]) + 1;
1939 p = strchrW(&env[len] + 1, '=');
1940 if (p)
1942 *p++ = 0;
1943 SetEnvironmentVariableW (&env[len], p);
1945 len += n;
1948 /* Restore current drive letter */
1949 if (IsCharAlphaW(temp->u.cwd)) {
1950 WCHAR envvar[4];
1951 WCHAR cwd[MAX_PATH];
1952 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1954 wsprintfW(envvar, fmt, temp->u.cwd);
1955 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1956 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1957 SetCurrentDirectoryW(cwd);
1961 LocalFree (env);
1962 LocalFree (temp);
1965 /*****************************************************************************
1966 * WCMD_setshow_attrib
1968 * Display and optionally sets DOS attributes on a file or directory
1972 void WCMD_setshow_attrib (void) {
1974 DWORD count;
1975 HANDLE hff;
1976 WIN32_FIND_DATAW fd;
1977 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1978 WCHAR *name = param1;
1979 DWORD attrib_set=0;
1980 DWORD attrib_clear=0;
1982 if (param1[0] == '+' || param1[0] == '-') {
1983 DWORD attrib = 0;
1984 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
1985 switch (param1[1]) {
1986 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
1987 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
1988 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
1989 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
1990 default:
1991 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1992 return;
1994 switch (param1[0]) {
1995 case '+': attrib_set = attrib; break;
1996 case '-': attrib_clear = attrib; break;
1998 name = param2;
2001 if (strlenW(name) == 0) {
2002 static const WCHAR slashStarW[] = {'\\','*','\0'};
2004 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
2005 strcatW (name, slashStarW);
2008 hff = FindFirstFileW(name, &fd);
2009 if (hff == INVALID_HANDLE_VALUE) {
2010 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
2012 else {
2013 do {
2014 if (attrib_set || attrib_clear) {
2015 fd.dwFileAttributes &= ~attrib_clear;
2016 fd.dwFileAttributes |= attrib_set;
2017 if (!fd.dwFileAttributes)
2018 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
2019 SetFileAttributesW(name, fd.dwFileAttributes);
2020 } else {
2021 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
2022 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
2023 flags[0] = 'H';
2025 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
2026 flags[1] = 'S';
2028 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
2029 flags[2] = 'A';
2031 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
2032 flags[3] = 'R';
2034 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
2035 flags[4] = 'T';
2037 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
2038 flags[5] = 'C';
2040 WCMD_output (fmt, flags, fd.cFileName);
2041 for (count=0; count < 8; count++) flags[count] = ' ';
2043 } while (FindNextFileW(hff, &fd) != 0);
2045 FindClose (hff);
2048 /*****************************************************************************
2049 * WCMD_setshow_default
2051 * Set/Show the current default directory
2054 void WCMD_setshow_default (WCHAR *command) {
2056 BOOL status;
2057 WCHAR string[1024];
2058 WCHAR cwd[1024];
2059 WCHAR *pos;
2060 WIN32_FIND_DATAW fd;
2061 HANDLE hff;
2062 static const WCHAR parmD[] = {'/','D','\0'};
2064 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2066 /* Skip /D and trailing whitespace if on the front of the command line */
2067 if (CompareStringW(LOCALE_USER_DEFAULT,
2068 NORM_IGNORECASE | SORT_STRINGSORT,
2069 command, 2, parmD, -1) == 2) {
2070 command += 2;
2071 while (*command && *command==' ') command++;
2074 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2075 if (strlenW(command) == 0) {
2076 strcatW (cwd, newline);
2077 WCMD_output (cwd);
2079 else {
2080 /* Remove any double quotes, which may be in the
2081 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2082 pos = string;
2083 while (*command) {
2084 if (*command != '"') *pos++ = *command;
2085 command++;
2087 *pos = 0x00;
2089 /* Search for appropriate directory */
2090 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2091 hff = FindFirstFileW(string, &fd);
2092 while (hff != INVALID_HANDLE_VALUE) {
2093 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2094 WCHAR fpath[MAX_PATH];
2095 WCHAR drive[10];
2096 WCHAR dir[MAX_PATH];
2097 WCHAR fname[MAX_PATH];
2098 WCHAR ext[MAX_PATH];
2099 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2101 /* Convert path into actual directory spec */
2102 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2103 WCMD_splitpath(fpath, drive, dir, fname, ext);
2105 /* Rebuild path */
2106 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2108 FindClose(hff);
2109 hff = INVALID_HANDLE_VALUE;
2110 break;
2113 /* Step on to next match */
2114 if (FindNextFileW(hff, &fd) == 0) {
2115 FindClose(hff);
2116 hff = INVALID_HANDLE_VALUE;
2117 break;
2121 /* Change to that directory */
2122 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2124 status = SetCurrentDirectoryW(string);
2125 if (!status) {
2126 errorlevel = 1;
2127 WCMD_print_error ();
2128 return;
2129 } else {
2131 /* Save away the actual new directory, to store as current location */
2132 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2134 /* Restore old directory if drive letter would change, and
2135 CD x:\directory /D (or pushd c:\directory) not supplied */
2136 if ((strstrW(quals, parmD) == NULL) &&
2137 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2138 SetCurrentDirectoryW(cwd);
2142 /* Set special =C: type environment variable, for drive letter of
2143 change of directory, even if path was restored due to missing
2144 /D (allows changing drive letter when not resident on that
2145 drive */
2146 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2147 WCHAR env[4];
2148 strcpyW(env, equalW);
2149 memcpy(env+1, string, 2 * sizeof(WCHAR));
2150 env[3] = 0x00;
2151 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2152 SetEnvironmentVariableW(env, string);
2156 return;
2159 /****************************************************************************
2160 * WCMD_setshow_date
2162 * Set/Show the system date
2163 * FIXME: Can't change date yet
2166 void WCMD_setshow_date (void) {
2168 WCHAR curdate[64], buffer[64];
2169 DWORD count;
2170 static const WCHAR parmT[] = {'/','T','\0'};
2172 if (strlenW(param1) == 0) {
2173 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2174 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2175 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2176 if (strstrW (quals, parmT) == NULL) {
2177 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2178 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2179 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2180 if (count > 2) {
2181 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2185 else WCMD_print_error ();
2187 else {
2188 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2192 /****************************************************************************
2193 * WCMD_compare
2195 static int WCMD_compare( const void *a, const void *b )
2197 int r;
2198 const WCHAR * const *str_a = a, * const *str_b = b;
2199 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2200 *str_a, -1, *str_b, -1 );
2201 if( r == CSTR_LESS_THAN ) return -1;
2202 if( r == CSTR_GREATER_THAN ) return 1;
2203 return 0;
2206 /****************************************************************************
2207 * WCMD_setshow_sortenv
2209 * sort variables into order for display
2210 * Optionally only display those who start with a stub
2211 * returns the count displayed
2213 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2215 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2216 const WCHAR **str;
2218 if (stub) stublen = strlenW(stub);
2220 /* count the number of strings, and the total length */
2221 while ( s[len] ) {
2222 len += (strlenW(&s[len]) + 1);
2223 count++;
2226 /* add the strings to an array */
2227 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2228 if( !str )
2229 return 0;
2230 str[0] = s;
2231 for( i=1; i<count; i++ )
2232 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2234 /* sort the array */
2235 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2237 /* print it */
2238 for( i=0; i<count; i++ ) {
2239 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2240 NORM_IGNORECASE | SORT_STRINGSORT,
2241 str[i], stublen, stub, -1) == 2) {
2242 /* Don't display special internal variables */
2243 if (str[i][0] != '=') {
2244 WCMD_output_asis(str[i]);
2245 WCMD_output_asis(newline);
2246 displayedcount++;
2251 LocalFree( str );
2252 return displayedcount;
2255 /****************************************************************************
2256 * WCMD_setshow_env
2258 * Set/Show the environment variables
2261 void WCMD_setshow_env (WCHAR *s) {
2263 LPVOID env;
2264 WCHAR *p;
2265 int status;
2266 static const WCHAR parmP[] = {'/','P','\0'};
2268 errorlevel = 0;
2269 if (param1[0] == 0x00 && quals[0] == 0x00) {
2270 env = GetEnvironmentStringsW();
2271 WCMD_setshow_sortenv( env, NULL );
2272 return;
2275 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2276 if (CompareStringW(LOCALE_USER_DEFAULT,
2277 NORM_IGNORECASE | SORT_STRINGSORT,
2278 s, 2, parmP, -1) == 2) {
2279 WCHAR string[MAXSTRING];
2280 DWORD count;
2282 s += 2;
2283 while (*s && *s==' ') s++;
2284 if (*s=='\"')
2285 WCMD_opt_s_strip_quotes(s);
2287 /* If no parameter, or no '=' sign, return an error */
2288 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2289 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2290 return;
2293 /* Output the prompt */
2294 *p++ = '\0';
2295 if (strlenW(p) != 0) WCMD_output(p);
2297 /* Read the reply */
2298 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2299 sizeof(string)/sizeof(WCHAR), &count, NULL);
2300 if (count > 1) {
2301 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2302 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2303 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2304 wine_dbgstr_w(string));
2305 status = SetEnvironmentVariableW(s, string);
2308 } else {
2309 DWORD gle;
2311 if (*s=='\"')
2312 WCMD_opt_s_strip_quotes(s);
2313 p = strchrW (s, '=');
2314 if (p == NULL) {
2315 env = GetEnvironmentStringsW();
2316 if (WCMD_setshow_sortenv( env, s ) == 0) {
2317 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2318 errorlevel = 1;
2320 return;
2322 *p++ = '\0';
2324 if (strlenW(p) == 0) p = NULL;
2325 status = SetEnvironmentVariableW(s, p);
2326 gle = GetLastError();
2327 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2328 errorlevel = 1;
2329 } else if ((!status)) WCMD_print_error();
2333 /****************************************************************************
2334 * WCMD_setshow_path
2336 * Set/Show the path environment variable
2339 void WCMD_setshow_path (WCHAR *command) {
2341 WCHAR string[1024];
2342 DWORD status;
2343 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2344 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2346 if (strlenW(param1) == 0) {
2347 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2348 if (status != 0) {
2349 WCMD_output_asis ( pathEqW);
2350 WCMD_output_asis ( string);
2351 WCMD_output_asis ( newline);
2353 else {
2354 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2357 else {
2358 if (*command == '=') command++; /* Skip leading '=' */
2359 status = SetEnvironmentVariableW(pathW, command);
2360 if (!status) WCMD_print_error();
2364 /****************************************************************************
2365 * WCMD_setshow_prompt
2367 * Set or show the command prompt.
2370 void WCMD_setshow_prompt (void) {
2372 WCHAR *s;
2373 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2375 if (strlenW(param1) == 0) {
2376 SetEnvironmentVariableW(promptW, NULL);
2378 else {
2379 s = param1;
2380 while ((*s == '=') || (*s == ' ')) s++;
2381 if (strlenW(s) == 0) {
2382 SetEnvironmentVariableW(promptW, NULL);
2384 else SetEnvironmentVariableW(promptW, s);
2388 /****************************************************************************
2389 * WCMD_setshow_time
2391 * Set/Show the system time
2392 * FIXME: Can't change time yet
2395 void WCMD_setshow_time (void) {
2397 WCHAR curtime[64], buffer[64];
2398 DWORD count;
2399 SYSTEMTIME st;
2400 static const WCHAR parmT[] = {'/','T','\0'};
2402 if (strlenW(param1) == 0) {
2403 GetLocalTime(&st);
2404 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2405 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2406 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2407 if (strstrW (quals, parmT) == NULL) {
2408 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2409 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2410 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2411 if (count > 2) {
2412 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2416 else WCMD_print_error ();
2418 else {
2419 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2423 /****************************************************************************
2424 * WCMD_shift
2426 * Shift batch parameters.
2427 * Optional /n says where to start shifting (n=0-8)
2430 void WCMD_shift (WCHAR *command) {
2431 int start;
2433 if (context != NULL) {
2434 WCHAR *pos = strchrW(command, '/');
2435 int i;
2437 if (pos == NULL) {
2438 start = 0;
2439 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2440 start = (*(pos+1) - '0');
2441 } else {
2442 SetLastError(ERROR_INVALID_PARAMETER);
2443 WCMD_print_error();
2444 return;
2447 WINE_TRACE("Shifting variables, starting at %d\n", start);
2448 for (i=start;i<=8;i++) {
2449 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2451 context -> shift_count[9] = context -> shift_count[9] + 1;
2456 /****************************************************************************
2457 * WCMD_title
2459 * Set the console title
2461 void WCMD_title (WCHAR *command) {
2462 SetConsoleTitleW(command);
2465 /****************************************************************************
2466 * WCMD_type
2468 * Copy a file to standard output.
2471 void WCMD_type (WCHAR *command) {
2473 int argno = 0;
2474 WCHAR *argN = command;
2475 BOOL writeHeaders = FALSE;
2477 if (param1[0] == 0x00) {
2478 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2479 return;
2482 if (param2[0] != 0x00) writeHeaders = TRUE;
2484 /* Loop through all args */
2485 errorlevel = 0;
2486 while (argN) {
2487 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2489 HANDLE h;
2490 WCHAR buffer[512];
2491 DWORD count;
2493 if (!argN) break;
2495 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2496 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2497 FILE_ATTRIBUTE_NORMAL, NULL);
2498 if (h == INVALID_HANDLE_VALUE) {
2499 WCMD_print_error ();
2500 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2501 errorlevel = 1;
2502 } else {
2503 if (writeHeaders) {
2504 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2505 WCMD_output(fmt, thisArg);
2507 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2508 if (count == 0) break; /* ReadFile reports success on EOF! */
2509 buffer[count] = 0;
2510 WCMD_output_asis (buffer);
2512 CloseHandle (h);
2513 if (!writeHeaders)
2514 WCMD_output_asis (newline);
2519 /****************************************************************************
2520 * WCMD_more
2522 * Output either a file or stdin to screen in pages
2525 void WCMD_more (WCHAR *command) {
2527 int argno = 0;
2528 WCHAR *argN = command;
2529 WCHAR moreStr[100];
2530 WCHAR moreStrPage[100];
2531 WCHAR buffer[512];
2532 DWORD count;
2533 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2534 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2535 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2536 ')',' ','-','-','\n','\0'};
2537 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2539 /* Prefix the NLS more with '-- ', then load the text */
2540 errorlevel = 0;
2541 strcpyW(moreStr, moreStart);
2542 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2543 (sizeof(moreStr)/sizeof(WCHAR))-3);
2545 if (param1[0] == 0x00) {
2547 /* Wine implements pipes via temporary files, and hence stdin is
2548 effectively reading from the file. This means the prompts for
2549 more are satisfied by the next line from the input (file). To
2550 avoid this, ensure stdin is to the console */
2551 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2552 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2553 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2554 FILE_ATTRIBUTE_NORMAL, 0);
2555 WINE_TRACE("No parms - working probably in pipe mode\n");
2556 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2558 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2559 once you get in this bit unless due to a pipe, its going to end badly... */
2560 wsprintfW(moreStrPage, moreFmt, moreStr);
2562 WCMD_enter_paged_mode(moreStrPage);
2563 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2564 if (count == 0) break; /* ReadFile reports success on EOF! */
2565 buffer[count] = 0;
2566 WCMD_output_asis (buffer);
2568 WCMD_leave_paged_mode();
2570 /* Restore stdin to what it was */
2571 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2572 CloseHandle(hConIn);
2574 return;
2575 } else {
2576 BOOL needsPause = FALSE;
2578 /* Loop through all args */
2579 WINE_TRACE("Parms supplied - working through each file\n");
2580 WCMD_enter_paged_mode(moreStrPage);
2582 while (argN) {
2583 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2584 HANDLE h;
2586 if (!argN) break;
2588 if (needsPause) {
2590 /* Wait */
2591 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2592 WCMD_leave_paged_mode();
2593 WCMD_output_asis(moreStrPage);
2594 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2595 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2596 WCMD_enter_paged_mode(moreStrPage);
2600 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2601 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2602 FILE_ATTRIBUTE_NORMAL, NULL);
2603 if (h == INVALID_HANDLE_VALUE) {
2604 WCMD_print_error ();
2605 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2606 errorlevel = 1;
2607 } else {
2608 ULONG64 curPos = 0;
2609 ULONG64 fileLen = 0;
2610 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2612 /* Get the file size */
2613 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2614 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2616 needsPause = TRUE;
2617 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2618 if (count == 0) break; /* ReadFile reports success on EOF! */
2619 buffer[count] = 0;
2620 curPos += count;
2622 /* Update % count (would be used in WCMD_output_asis as prompt) */
2623 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2625 WCMD_output_asis (buffer);
2627 CloseHandle (h);
2631 WCMD_leave_paged_mode();
2635 /****************************************************************************
2636 * WCMD_verify
2638 * Display verify flag.
2639 * FIXME: We don't actually do anything with the verify flag other than toggle
2640 * it...
2643 void WCMD_verify (WCHAR *command) {
2645 int count;
2647 count = strlenW(command);
2648 if (count == 0) {
2649 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2650 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2651 return;
2653 if (lstrcmpiW(command, onW) == 0) {
2654 verify_mode = 1;
2655 return;
2657 else if (lstrcmpiW(command, offW) == 0) {
2658 verify_mode = 0;
2659 return;
2661 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2664 /****************************************************************************
2665 * WCMD_version
2667 * Display version info.
2670 void WCMD_version (void) {
2672 WCMD_output (version_string);
2676 /****************************************************************************
2677 * WCMD_volume
2679 * Display volume info and/or set volume label. Returns 0 if error.
2682 int WCMD_volume (int mode, WCHAR *path) {
2684 DWORD count, serial;
2685 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2686 BOOL status;
2688 if (strlenW(path) == 0) {
2689 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2690 if (!status) {
2691 WCMD_print_error ();
2692 return 0;
2694 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2695 &serial, NULL, NULL, NULL, 0);
2697 else {
2698 static const WCHAR fmt[] = {'%','s','\\','\0'};
2699 if ((path[1] != ':') || (strlenW(path) != 2)) {
2700 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2701 return 0;
2703 wsprintfW (curdir, fmt, path);
2704 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2705 &serial, NULL,
2706 NULL, NULL, 0);
2708 if (!status) {
2709 WCMD_print_error ();
2710 return 0;
2712 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2713 curdir[0], label, HIWORD(serial), LOWORD(serial));
2714 if (mode) {
2715 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2716 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2717 sizeof(string)/sizeof(WCHAR), &count, NULL);
2718 if (count > 1) {
2719 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2720 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2722 if (strlenW(path) != 0) {
2723 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2725 else {
2726 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2729 return 1;
2732 /**************************************************************************
2733 * WCMD_exit
2735 * Exit either the process, or just this batch program
2739 void WCMD_exit (CMD_LIST **cmdList) {
2741 static const WCHAR parmB[] = {'/','B','\0'};
2742 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2744 if (context && lstrcmpiW(quals, parmB) == 0) {
2745 errorlevel = rc;
2746 context -> skip_rest = TRUE;
2747 *cmdList = NULL;
2748 } else {
2749 ExitProcess(rc);
2754 /*****************************************************************************
2755 * WCMD_assoc
2757 * Lists or sets file associations (assoc = TRUE)
2758 * Lists or sets file types (assoc = FALSE)
2760 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2762 HKEY key;
2763 DWORD accessOptions = KEY_READ;
2764 WCHAR *newValue;
2765 LONG rc = ERROR_SUCCESS;
2766 WCHAR keyValue[MAXSTRING];
2767 DWORD valueLen = MAXSTRING;
2768 HKEY readKey;
2769 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2770 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2772 /* See if parameter includes '=' */
2773 errorlevel = 0;
2774 newValue = strchrW(command, '=');
2775 if (newValue) accessOptions |= KEY_WRITE;
2777 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2778 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2779 accessOptions, &key) != ERROR_SUCCESS) {
2780 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2781 return;
2784 /* If no parameters then list all associations */
2785 if (*command == 0x00) {
2786 int index = 0;
2788 /* Enumerate all the keys */
2789 while (rc != ERROR_NO_MORE_ITEMS) {
2790 WCHAR keyName[MAXSTRING];
2791 DWORD nameLen;
2793 /* Find the next value */
2794 nameLen = MAXSTRING;
2795 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2797 if (rc == ERROR_SUCCESS) {
2799 /* Only interested in extension ones if assoc, or others
2800 if not assoc */
2801 if ((keyName[0] == '.' && assoc) ||
2802 (!(keyName[0] == '.') && (!assoc)))
2804 WCHAR subkey[MAXSTRING];
2805 strcpyW(subkey, keyName);
2806 if (!assoc) strcatW(subkey, shOpCmdW);
2808 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2810 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2811 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2812 WCMD_output_asis(keyName);
2813 WCMD_output_asis(equalW);
2814 /* If no default value found, leave line empty after '=' */
2815 if (rc == ERROR_SUCCESS) {
2816 WCMD_output_asis(keyValue);
2818 WCMD_output_asis(newline);
2819 RegCloseKey(readKey);
2825 } else {
2827 /* Parameter supplied - if no '=' on command line, its a query */
2828 if (newValue == NULL) {
2829 WCHAR *space;
2830 WCHAR subkey[MAXSTRING];
2832 /* Query terminates the parameter at the first space */
2833 strcpyW(keyValue, command);
2834 space = strchrW(keyValue, ' ');
2835 if (space) *space=0x00;
2837 /* Set up key name */
2838 strcpyW(subkey, keyValue);
2839 if (!assoc) strcatW(subkey, shOpCmdW);
2841 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2843 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2844 WCMD_output_asis(command);
2845 WCMD_output_asis(equalW);
2846 /* If no default value found, leave line empty after '=' */
2847 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2848 WCMD_output_asis(newline);
2849 RegCloseKey(readKey);
2851 } else {
2852 WCHAR msgbuffer[MAXSTRING];
2853 WCHAR outbuffer[MAXSTRING];
2855 /* Load the translated 'File association not found' */
2856 if (assoc) {
2857 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2858 } else {
2859 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2861 wsprintfW(outbuffer, msgbuffer, keyValue);
2862 WCMD_output_asis(outbuffer);
2863 errorlevel = 2;
2866 /* Not a query - its a set or clear of a value */
2867 } else {
2869 WCHAR subkey[MAXSTRING];
2871 /* Get pointer to new value */
2872 *newValue = 0x00;
2873 newValue++;
2875 /* Set up key name */
2876 strcpyW(subkey, command);
2877 if (!assoc) strcatW(subkey, shOpCmdW);
2879 /* If nothing after '=' then clear value - only valid for ASSOC */
2880 if (*newValue == 0x00) {
2882 if (assoc) rc = RegDeleteKeyW(key, command);
2883 if (assoc && rc == ERROR_SUCCESS) {
2884 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2886 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2887 WCMD_print_error();
2888 errorlevel = 2;
2890 } else {
2891 WCHAR msgbuffer[MAXSTRING];
2892 WCHAR outbuffer[MAXSTRING];
2894 /* Load the translated 'File association not found' */
2895 if (assoc) {
2896 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2897 sizeof(msgbuffer)/sizeof(WCHAR));
2898 } else {
2899 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2900 sizeof(msgbuffer)/sizeof(WCHAR));
2902 wsprintfW(outbuffer, msgbuffer, keyValue);
2903 WCMD_output_asis(outbuffer);
2904 errorlevel = 2;
2907 /* It really is a set value = contents */
2908 } else {
2909 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2910 accessOptions, NULL, &readKey, NULL);
2911 if (rc == ERROR_SUCCESS) {
2912 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2913 (LPBYTE)newValue, strlenW(newValue));
2914 RegCloseKey(readKey);
2917 if (rc != ERROR_SUCCESS) {
2918 WCMD_print_error();
2919 errorlevel = 2;
2920 } else {
2921 WCMD_output_asis(command);
2922 WCMD_output_asis(equalW);
2923 WCMD_output_asis(newValue);
2924 WCMD_output_asis(newline);
2930 /* Clean up */
2931 RegCloseKey(key);
2934 /****************************************************************************
2935 * WCMD_color
2937 * Clear the terminal screen.
2940 void WCMD_color (void) {
2942 /* Emulate by filling the screen from the top left to bottom right with
2943 spaces, then moving the cursor to the top left afterwards */
2944 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2945 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2947 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2948 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2949 return;
2952 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2954 COORD topLeft;
2955 DWORD screenSize;
2956 DWORD color = 0;
2958 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2960 topLeft.X = 0;
2961 topLeft.Y = 0;
2963 /* Convert the color hex digits */
2964 if (param1[0] == 0x00) {
2965 color = defaultColor;
2966 } else {
2967 color = strtoulW(param1, NULL, 16);
2970 /* Fail if fg == bg color */
2971 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2972 errorlevel = 1;
2973 return;
2976 /* Set the current screen contents and ensure all future writes
2977 remain this color */
2978 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2979 SetConsoleTextAttribute(hStdOut, color);