cmd: mkdir: Set errorlevel and output error message if final directory already exists.
[wine.git] / programs / cmd / builtins.c
blob09c166fb0f1133dbb98b3629b3dce4e05e91482c
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 (and, if needed, any intermediate directories).
478 * Modifies its argument by replacing slashes temporarily with nulls.
481 static BOOL create_full_path(WCHAR* path)
483 WCHAR *p, *start;
485 /* don't mess with drive letter portion of path, if any */
486 start = path;
487 if (path[1] == ':')
488 start = path+2;
490 /* Strip trailing slashes. */
491 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
492 *p = 0;
494 /* Step through path, creating intermediate directories as needed. */
495 /* First component includes drive letter, if any. */
496 p = start;
497 for (;;) {
498 DWORD rv;
499 /* Skip to end of component */
500 while (*p == '\\') p++;
501 while (*p && *p != '\\') p++;
502 if (!*p) {
503 /* path is now the original full path */
504 return CreateDirectoryW(path, NULL);
506 /* Truncate path, create intermediate directory, and restore path */
507 *p = 0;
508 rv = CreateDirectoryW(path, NULL);
509 *p = '\\';
510 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
511 return FALSE;
513 /* notreached */
514 return FALSE;
517 void WCMD_create_dir (WCHAR *command) {
518 int argno = 0;
519 WCHAR *argN = command;
521 if (param1[0] == 0x00) {
522 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
523 return;
525 /* Loop through all args */
526 while (TRUE) {
527 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN);
528 if (!argN) break;
529 if (!create_full_path(thisArg)) {
530 WCMD_print_error ();
531 errorlevel = 1;
536 /* Parse the /A options given by the user on the commandline
537 * into a bitmask of wanted attributes (*wantSet),
538 * and a bitmask of unwanted attributes (*wantClear).
540 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
541 static const WCHAR parmA[] = {'/','A','\0'};
542 WCHAR *p;
544 /* both are strictly 'out' parameters */
545 *wantSet=0;
546 *wantClear=0;
548 /* For each /A argument */
549 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
550 /* Skip /A itself */
551 p += 2;
553 /* Skip optional : */
554 if (*p == ':') p++;
556 /* For each of the attribute specifier chars to this /A option */
557 for (; *p != 0 && *p != '/'; p++) {
558 BOOL negate = FALSE;
559 DWORD mask = 0;
561 if (*p == '-') {
562 negate=TRUE;
563 p++;
566 /* Convert the attribute specifier to a bit in one of the masks */
567 switch (*p) {
568 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
569 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
570 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
571 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
572 default:
573 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
575 if (negate)
576 *wantClear |= mask;
577 else
578 *wantSet |= mask;
583 /* If filename part of parameter is * or *.*,
584 * and neither /Q nor /P options were given,
585 * prompt the user whether to proceed.
586 * Returns FALSE if user says no, TRUE otherwise.
587 * *pPrompted is set to TRUE if the user is prompted.
588 * (If /P supplied, del will prompt for individual files later.)
590 static BOOL WCMD_delete_confirm_wildcard(WCHAR *filename, BOOL *pPrompted) {
591 static const WCHAR parmP[] = {'/','P','\0'};
592 static const WCHAR parmQ[] = {'/','Q','\0'};
594 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
595 static const WCHAR anyExt[]= {'.','*','\0'};
596 WCHAR drive[10];
597 WCHAR dir[MAX_PATH];
598 WCHAR fname[MAX_PATH];
599 WCHAR ext[MAX_PATH];
600 WCHAR fpath[MAX_PATH];
602 /* Convert path into actual directory spec */
603 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
604 WCMD_splitpath(fpath, drive, dir, fname, ext);
606 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
607 if ((strcmpW(fname, starW) == 0) &&
608 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
610 WCHAR question[MAXSTRING];
611 static const WCHAR fmt[] = {'%','s',' ','\0'};
613 /* Caller uses this to suppress "file not found" warning later */
614 *pPrompted = TRUE;
616 /* Ask for confirmation */
617 wsprintfW(question, fmt, fpath);
618 return WCMD_ask_confirm(question, TRUE, NULL);
621 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
622 return TRUE;
625 /* Helper function for WCMD_delete().
626 * Deletes a single file, directory, or wildcard.
627 * If /S was given, does it recursively.
628 * Returns TRUE if a file was deleted.
630 static BOOL WCMD_delete_one (WCHAR *thisArg) {
632 static const WCHAR parmP[] = {'/','P','\0'};
633 static const WCHAR parmS[] = {'/','S','\0'};
634 static const WCHAR parmF[] = {'/','F','\0'};
635 DWORD wanted_attrs;
636 DWORD unwanted_attrs;
637 BOOL found = FALSE;
638 WCHAR argCopy[MAX_PATH];
639 WIN32_FIND_DATAW fd;
640 HANDLE hff;
641 WCHAR fpath[MAX_PATH];
642 WCHAR *p;
643 BOOL handleParm = TRUE;
645 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
647 strcpyW(argCopy, thisArg);
648 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
649 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
651 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
652 /* Skip this arg if user declines to delete *.* */
653 return FALSE;
656 /* First, try to delete in the current directory */
657 hff = FindFirstFileW(argCopy, &fd);
658 if (hff == INVALID_HANDLE_VALUE) {
659 handleParm = FALSE;
660 } else {
661 found = TRUE;
664 /* Support del <dirname> by just deleting all files dirname\* */
665 if (handleParm
666 && (strchrW(argCopy,'*') == NULL)
667 && (strchrW(argCopy,'?') == NULL)
668 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
670 WCHAR modifiedParm[MAX_PATH];
671 static const WCHAR slashStar[] = {'\\','*','\0'};
673 strcpyW(modifiedParm, argCopy);
674 strcatW(modifiedParm, slashStar);
675 FindClose(hff);
676 found = TRUE;
677 WCMD_delete_one(modifiedParm);
679 } else if (handleParm) {
681 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
682 strcpyW (fpath, argCopy);
683 do {
684 p = strrchrW (fpath, '\\');
685 if (p != NULL) {
686 *++p = '\0';
687 strcatW (fpath, fd.cFileName);
689 else strcpyW (fpath, fd.cFileName);
690 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
691 BOOL ok;
693 /* Handle attribute matching (/A) */
694 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
695 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
697 /* /P means prompt for each file */
698 if (ok && strstrW (quals, parmP) != NULL) {
699 WCHAR question[MAXSTRING];
701 /* Ask for confirmation */
702 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
703 ok = WCMD_ask_confirm(question, FALSE, NULL);
706 /* Only proceed if ok to */
707 if (ok) {
709 /* If file is read only, and /A:r or /F supplied, delete it */
710 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
711 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
712 strstrW (quals, parmF) != NULL)) {
713 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
716 /* Now do the delete */
717 if (!DeleteFileW(fpath)) WCMD_print_error ();
721 } while (FindNextFileW(hff, &fd) != 0);
722 FindClose (hff);
725 /* Now recurse into all subdirectories handling the parameter in the same way */
726 if (strstrW (quals, parmS) != NULL) {
728 WCHAR thisDir[MAX_PATH];
729 int cPos;
731 WCHAR drive[10];
732 WCHAR dir[MAX_PATH];
733 WCHAR fname[MAX_PATH];
734 WCHAR ext[MAX_PATH];
736 /* Convert path into actual directory spec */
737 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
738 WCMD_splitpath(thisDir, drive, dir, fname, ext);
740 strcpyW(thisDir, drive);
741 strcatW(thisDir, dir);
742 cPos = strlenW(thisDir);
744 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
746 /* Append '*' to the directory */
747 thisDir[cPos] = '*';
748 thisDir[cPos+1] = 0x00;
750 hff = FindFirstFileW(thisDir, &fd);
752 /* Remove residual '*' */
753 thisDir[cPos] = 0x00;
755 if (hff != INVALID_HANDLE_VALUE) {
756 DIRECTORY_STACK *allDirs = NULL;
757 DIRECTORY_STACK *lastEntry = NULL;
759 do {
760 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
761 (strcmpW(fd.cFileName, dotdotW) != 0) &&
762 (strcmpW(fd.cFileName, dotW) != 0)) {
764 DIRECTORY_STACK *nextDir;
765 WCHAR subParm[MAX_PATH];
767 /* Work out search parameter in sub dir */
768 strcpyW (subParm, thisDir);
769 strcatW (subParm, fd.cFileName);
770 strcatW (subParm, slashW);
771 strcatW (subParm, fname);
772 strcatW (subParm, ext);
773 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
775 /* Allocate memory, add to list */
776 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
777 if (allDirs == NULL) allDirs = nextDir;
778 if (lastEntry != NULL) lastEntry->next = nextDir;
779 lastEntry = nextDir;
780 nextDir->next = NULL;
781 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
782 (strlenW(subParm)+1) * sizeof(WCHAR));
783 strcpyW(nextDir->dirName, subParm);
785 } while (FindNextFileW(hff, &fd) != 0);
786 FindClose (hff);
788 /* Go through each subdir doing the delete */
789 while (allDirs != NULL) {
790 DIRECTORY_STACK *tempDir;
792 tempDir = allDirs->next;
793 found |= WCMD_delete_one (allDirs->dirName);
795 HeapFree(GetProcessHeap(),0,allDirs->dirName);
796 HeapFree(GetProcessHeap(),0,allDirs);
797 allDirs = tempDir;
802 return found;
805 /****************************************************************************
806 * WCMD_delete
808 * Delete a file or wildcarded set.
810 * Note on /A:
811 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
812 * - Each set is a pattern, eg /ahr /as-r means
813 * readonly+hidden OR nonreadonly system files
814 * - The '-' applies to a single field, ie /a:-hr means read only
815 * non-hidden files
818 BOOL WCMD_delete (WCHAR *command) {
819 int argno;
820 WCHAR *argN;
821 BOOL argsProcessed = FALSE;
822 BOOL foundAny = FALSE;
824 errorlevel = 0;
826 for (argno=0; ; argno++) {
827 BOOL found;
828 WCHAR *thisArg;
830 argN = NULL;
831 thisArg = WCMD_parameter (command, argno, &argN);
832 if (!argN)
833 break; /* no more parameters */
834 if (argN[0] == '/')
835 continue; /* skip options */
837 argsProcessed = TRUE;
838 found = WCMD_delete_one(thisArg);
839 if (!found) {
840 errorlevel = 1;
841 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
843 foundAny |= found;
846 /* Handle no valid args */
847 if (!argsProcessed)
848 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
850 return foundAny;
853 /****************************************************************************
854 * WCMD_echo
856 * Echo input to the screen (or not). We don't try to emulate the bugs
857 * in DOS (try typing "ECHO ON AGAIN" for an example).
860 void WCMD_echo (const WCHAR *command) {
862 int count;
863 const WCHAR *origcommand = command;
865 if (command[0]==' ' || command[0]=='.' || command[0]==':')
866 command++;
867 count = strlenW(command);
868 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':') {
869 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
870 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
871 return;
873 if (lstrcmpiW(command, onW) == 0) {
874 echo_mode = 1;
875 return;
877 if (lstrcmpiW(command, offW) == 0) {
878 echo_mode = 0;
879 return;
881 WCMD_output_asis (command);
882 WCMD_output (newline);
886 /**************************************************************************
887 * WCMD_for
889 * Batch file loop processing.
891 * On entry: cmdList contains the syntax up to the set
892 * next cmdList and all in that bracket contain the set data
893 * next cmdlist contains the DO cmd
894 * following that is either brackets or && entries (as per if)
898 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
900 WIN32_FIND_DATAW fd;
901 HANDLE hff;
902 int i;
903 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
904 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
905 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
906 WCHAR variable[4];
907 WCHAR *firstCmd;
908 int thisDepth;
910 WCHAR *curPos = p;
911 BOOL expandDirs = FALSE;
912 BOOL useNumbers = FALSE;
913 BOOL doFileset = FALSE;
914 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
915 int itemNum;
916 CMD_LIST *thisCmdStart;
919 /* Handle optional qualifiers (multiple are allowed) */
920 while (*curPos && *curPos == '/') {
921 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
922 curPos++;
923 switch (toupperW(*curPos)) {
924 case 'D': curPos++; expandDirs = TRUE; break;
925 case 'L': curPos++; useNumbers = TRUE; break;
927 /* Recursive is special case - /R can have an optional path following it */
928 /* filenamesets are another special case - /F can have an optional options following it */
929 case 'R':
930 case 'F':
932 BOOL isRecursive = (*curPos == 'R');
934 if (!isRecursive)
935 doFileset = TRUE;
937 /* Skip whitespace */
938 curPos++;
939 while (*curPos && *curPos==' ') curPos++;
941 /* Next parm is either qualifier, path/options or variable -
942 only care about it if it is the path/options */
943 if (*curPos && *curPos != '/' && *curPos != '%') {
944 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
945 else WINE_FIXME("/F needs to handle options\n");
947 break;
949 default:
950 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
951 curPos++;
954 /* Skip whitespace between qualifiers */
955 while (*curPos && *curPos==' ') curPos++;
958 /* Skip whitespace before variable */
959 while (*curPos && *curPos==' ') curPos++;
961 /* Ensure line continues with variable */
962 if (!*curPos || *curPos != '%') {
963 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
964 return;
967 /* Variable should follow */
968 i = 0;
969 while (curPos[i] && curPos[i]!=' ') i++;
970 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
971 variable[i] = 0x00;
972 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
973 curPos = &curPos[i];
975 /* Skip whitespace before IN */
976 while (*curPos && *curPos==' ') curPos++;
978 /* Ensure line continues with IN */
979 if (!*curPos || lstrcmpiW (curPos, inW)) {
980 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
981 return;
984 /* Save away where the set of data starts and the variable */
985 thisDepth = (*cmdList)->bracketDepth;
986 *cmdList = (*cmdList)->nextcommand;
987 setStart = (*cmdList);
989 /* Skip until the close bracket */
990 WINE_TRACE("Searching %p as the set\n", *cmdList);
991 while (*cmdList &&
992 (*cmdList)->command != NULL &&
993 (*cmdList)->bracketDepth > thisDepth) {
994 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
995 *cmdList = (*cmdList)->nextcommand;
998 /* Skip the close bracket, if there is one */
999 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1001 /* Syntax error if missing close bracket, or nothing following it
1002 and once we have the complete set, we expect a DO */
1003 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
1004 if ((*cmdList == NULL) ||
1005 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1006 (*cmdList)->command, 3, doW, -1) != 2)) {
1007 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1008 return;
1011 /* Save away the starting position for the commands (and offset for the
1012 first one */
1013 cmdStart = *cmdList;
1014 cmdEnd = *cmdList;
1015 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1016 itemNum = 0;
1018 thisSet = setStart;
1019 /* Loop through all set entries */
1020 while (thisSet &&
1021 thisSet->command != NULL &&
1022 thisSet->bracketDepth >= thisDepth) {
1024 /* Loop through all entries on the same line */
1025 WCHAR *item;
1026 WCHAR *itemStart;
1028 WINE_TRACE("Processing for set %p\n", thisSet);
1029 i = 0;
1030 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
1033 * If the parameter within the set has a wildcard then search for matching files
1034 * otherwise do a literal substitution.
1036 static const WCHAR wildcards[] = {'*','?','\0'};
1037 thisCmdStart = cmdStart;
1039 itemNum++;
1040 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1042 if (!useNumbers && !doFileset) {
1043 if (strpbrkW (item, wildcards)) {
1044 hff = FindFirstFileW(item, &fd);
1045 if (hff != INVALID_HANDLE_VALUE) {
1046 do {
1047 BOOL isDirectory = FALSE;
1049 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1051 /* Handle as files or dirs appropriately, but ignore . and .. */
1052 if (isDirectory == expandDirs &&
1053 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1054 (strcmpW(fd.cFileName, dotW) != 0))
1056 thisCmdStart = cmdStart;
1057 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1058 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1059 fd.cFileName, FALSE, TRUE);
1062 } while (FindNextFileW(hff, &fd) != 0);
1063 FindClose (hff);
1065 } else {
1066 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1069 } else if (useNumbers) {
1070 /* Convert the first 3 numbers to signed longs and save */
1071 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1072 /* else ignore them! */
1074 /* Filesets - either a list of files, or a command to run and parse the output */
1075 } else if (doFileset && *itemStart != '"') {
1077 HANDLE input;
1078 WCHAR temp_file[MAX_PATH];
1080 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1081 wine_dbgstr_w(item));
1083 /* If backquote or single quote, we need to launch that command
1084 and parse the results - use a temporary file */
1085 if (*itemStart == '`' || *itemStart == '\'') {
1087 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1088 static const WCHAR redirOut[] = {'>','%','s','\0'};
1089 static const WCHAR cmdW[] = {'C','M','D','\0'};
1091 /* Remove trailing character */
1092 itemStart[strlenW(itemStart)-1] = 0x00;
1094 /* Get temp filename */
1095 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1096 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1098 /* Execute program and redirect output */
1099 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1100 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1102 /* Open the file, read line by line and process */
1103 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1104 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1105 } else {
1107 /* Open the file, read line by line and process */
1108 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1109 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1112 /* Process the input file */
1113 if (input == INVALID_HANDLE_VALUE) {
1114 WCMD_print_error ();
1115 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1116 errorlevel = 1;
1117 return; /* FOR loop aborts at first failure here */
1119 } else {
1121 WCHAR buffer[MAXSTRING] = {'\0'};
1122 WCHAR *where, *parm;
1124 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1126 /* Skip blank lines*/
1127 parm = WCMD_parameter (buffer, 0, &where);
1128 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1129 wine_dbgstr_w(buffer));
1131 if (where) {
1132 /* FIXME: The following should be moved into its own routine and
1133 reused for the string literal parsing below */
1134 thisCmdStart = cmdStart;
1135 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1136 cmdEnd = thisCmdStart;
1139 buffer[0] = 0x00;
1142 CloseHandle (input);
1145 /* Delete the temporary file */
1146 if (*itemStart == '`' || *itemStart == '\'') {
1147 DeleteFileW(temp_file);
1150 /* Filesets - A string literal */
1151 } else if (doFileset && *itemStart == '"') {
1152 WCHAR buffer[MAXSTRING] = {'\0'};
1153 WCHAR *where, *parm;
1155 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1156 strcpyW(buffer, item);
1157 parm = WCMD_parameter (buffer, 0, &where);
1158 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1159 wine_dbgstr_w(buffer));
1161 if (where) {
1162 /* FIXME: The following should be moved into its own routine and
1163 reused for the string literal parsing below */
1164 thisCmdStart = cmdStart;
1165 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1166 cmdEnd = thisCmdStart;
1170 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1171 cmdEnd = thisCmdStart;
1172 i++;
1175 /* Move onto the next set line */
1176 thisSet = thisSet->nextcommand;
1179 /* If /L is provided, now run the for loop */
1180 if (useNumbers) {
1181 WCHAR thisNum[20];
1182 static const WCHAR fmt[] = {'%','d','\0'};
1184 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1185 numbers[0], numbers[2], numbers[1]);
1186 for (i=numbers[0];
1187 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1188 i=i + numbers[1]) {
1190 sprintfW(thisNum, fmt, i);
1191 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1193 thisCmdStart = cmdStart;
1194 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1195 cmdEnd = thisCmdStart;
1199 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1200 all processing, OR it should be pointing to the end of && processing OR
1201 it should be pointing at the NULL end of bracket for the DO. The return
1202 value needs to be the NEXT command to execute, which it either is, or
1203 we need to step over the closing bracket */
1204 *cmdList = cmdEnd;
1205 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1209 /*****************************************************************************
1210 * WCMD_part_execute
1212 * Execute a command, and any && or bracketed follow on to the command. The
1213 * first command to be executed may not be at the front of the
1214 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1216 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1217 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1219 CMD_LIST *curPosition = *cmdList;
1220 int myDepth = (*cmdList)->bracketDepth;
1222 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1223 cmdList, wine_dbgstr_w(firstcmd),
1224 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1225 conditionTRUE);
1227 /* Skip leading whitespace between condition and the command */
1228 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1230 /* Process the first command, if there is one */
1231 if (conditionTRUE && firstcmd && *firstcmd) {
1232 WCHAR *command = WCMD_strdupW(firstcmd);
1233 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1234 HeapFree(GetProcessHeap(), 0, command);
1238 /* If it didn't move the position, step to next command */
1239 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1241 /* Process any other parts of the command */
1242 if (*cmdList) {
1243 BOOL processThese = TRUE;
1245 if (isIF) processThese = conditionTRUE;
1247 while (*cmdList) {
1248 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1250 /* execute all appropriate commands */
1251 curPosition = *cmdList;
1253 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1254 *cmdList,
1255 (*cmdList)->prevDelim,
1256 (*cmdList)->bracketDepth, myDepth);
1258 /* Execute any statements appended to the line */
1259 /* FIXME: Only if previous call worked for && or failed for || */
1260 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1261 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1262 if (processThese && (*cmdList)->command) {
1263 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1264 value, cmdList);
1266 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1268 /* Execute any appended to the statement with (...) */
1269 } else if ((*cmdList)->bracketDepth > myDepth) {
1270 if (processThese) {
1271 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1272 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1274 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1276 /* End of the command - does 'ELSE ' follow as the next command? */
1277 } else {
1278 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1279 NORM_IGNORECASE | SORT_STRINGSORT,
1280 (*cmdList)->command, 5, ifElse, -1) == 2) {
1282 /* Swap between if and else processing */
1283 processThese = !processThese;
1285 /* Process the ELSE part */
1286 if (processThese) {
1287 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1289 /* Skip leading whitespace between condition and the command */
1290 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1291 if (*cmd) {
1292 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1295 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1296 } else {
1297 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1298 break;
1303 return;
1306 /**************************************************************************
1307 * WCMD_give_help
1309 * Simple on-line help. Help text is stored in the resource file.
1312 void WCMD_give_help (WCHAR *command) {
1314 int i;
1316 command = WCMD_strtrim_leading_spaces(command);
1317 if (strlenW(command) == 0) {
1318 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1320 else {
1321 for (i=0; i<=WCMD_EXIT; i++) {
1322 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1323 command, -1, inbuilt[i], -1) == 2) {
1324 WCMD_output_asis (WCMD_LoadMessage(i));
1325 return;
1328 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1330 return;
1333 /****************************************************************************
1334 * WCMD_go_to
1336 * Batch file jump instruction. Not the most efficient algorithm ;-)
1337 * Prints error message if the specified label cannot be found - the file pointer is
1338 * then at EOF, effectively stopping the batch file.
1339 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1342 void WCMD_goto (CMD_LIST **cmdList) {
1344 WCHAR string[MAX_PATH];
1345 WCHAR current[MAX_PATH];
1347 /* Do not process any more parts of a processed multipart or multilines command */
1348 if (cmdList) *cmdList = NULL;
1350 if (param1[0] == 0x00) {
1351 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1352 return;
1354 if (context != NULL) {
1355 WCHAR *paramStart = param1, *str;
1356 static const WCHAR eofW[] = {':','e','o','f','\0'};
1358 /* Handle special :EOF label */
1359 if (lstrcmpiW (eofW, param1) == 0) {
1360 context -> skip_rest = TRUE;
1361 return;
1364 /* Support goto :label as well as goto label */
1365 if (*paramStart == ':') paramStart++;
1367 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1368 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1369 str = string;
1370 while (isspaceW (*str)) str++;
1371 if (*str == ':') {
1372 DWORD index = 0;
1373 str++;
1374 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1375 index++;
1377 /* ignore space at the end */
1378 current[index] = 0;
1379 if (lstrcmpiW (current, paramStart) == 0) return;
1382 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1384 return;
1387 /*****************************************************************************
1388 * WCMD_pushd
1390 * Push a directory onto the stack
1393 void WCMD_pushd (WCHAR *command) {
1394 struct env_stack *curdir;
1395 WCHAR *thisdir;
1396 static const WCHAR parmD[] = {'/','D','\0'};
1398 if (strchrW(command, '/') != NULL) {
1399 SetLastError(ERROR_INVALID_PARAMETER);
1400 WCMD_print_error();
1401 return;
1404 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1405 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1406 if( !curdir || !thisdir ) {
1407 LocalFree(curdir);
1408 LocalFree(thisdir);
1409 WINE_ERR ("out of memory\n");
1410 return;
1413 /* Change directory using CD code with /D parameter */
1414 strcpyW(quals, parmD);
1415 GetCurrentDirectoryW (1024, thisdir);
1416 errorlevel = 0;
1417 WCMD_setshow_default(command);
1418 if (errorlevel) {
1419 LocalFree(curdir);
1420 LocalFree(thisdir);
1421 return;
1422 } else {
1423 curdir -> next = pushd_directories;
1424 curdir -> strings = thisdir;
1425 if (pushd_directories == NULL) {
1426 curdir -> u.stackdepth = 1;
1427 } else {
1428 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1430 pushd_directories = curdir;
1435 /*****************************************************************************
1436 * WCMD_popd
1438 * Pop a directory from the stack
1441 void WCMD_popd (void) {
1442 struct env_stack *temp = pushd_directories;
1444 if (!pushd_directories)
1445 return;
1447 /* pop the old environment from the stack, and make it the current dir */
1448 pushd_directories = temp->next;
1449 SetCurrentDirectoryW(temp->strings);
1450 LocalFree (temp->strings);
1451 LocalFree (temp);
1454 /****************************************************************************
1455 * WCMD_if
1457 * Batch file conditional.
1459 * On entry, cmdlist will point to command containing the IF, and optionally
1460 * the first command to execute (if brackets not found)
1461 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1462 * If ('s were found, execute all within that bracket
1463 * Command may optionally be followed by an ELSE - need to skip instructions
1464 * in the else using the same logic
1466 * FIXME: Much more syntax checking needed!
1469 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1471 int negate = 0, test = 0;
1472 WCHAR condition[MAX_PATH], *command, *s;
1473 static const WCHAR notW[] = {'n','o','t','\0'};
1474 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1475 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1476 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1477 static const WCHAR eqeqW[] = {'=','=','\0'};
1478 static const WCHAR parmI[] = {'/','I','\0'};
1480 if (!lstrcmpiW (param1, notW)) {
1481 negate = 1;
1482 strcpyW (condition, param2);
1484 else {
1485 strcpyW (condition, param1);
1487 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1489 if (!lstrcmpiW (condition, errlvlW)) {
1490 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1491 WCMD_parameter (p, 2+negate, &command);
1493 else if (!lstrcmpiW (condition, existW)) {
1494 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1495 test = 1;
1497 WCMD_parameter (p, 2+negate, &command);
1499 else if (!lstrcmpiW (condition, defdW)) {
1500 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1501 test = 1;
1503 WCMD_parameter (p, 2+negate, &command);
1505 else if ((s = strstrW (p, eqeqW))) {
1506 s += 2;
1507 if (strstrW (quals, parmI) == NULL) {
1508 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1510 else {
1511 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1513 WCMD_parameter (s, 1, &command);
1515 else {
1516 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1517 return;
1520 /* Process rest of IF statement which is on the same line
1521 Note: This may process all or some of the cmdList (eg a GOTO) */
1522 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1525 /****************************************************************************
1526 * WCMD_move
1528 * Move a file, directory tree or wildcarded set of files.
1531 void WCMD_move (void) {
1533 int status;
1534 WIN32_FIND_DATAW fd;
1535 HANDLE hff;
1536 WCHAR input[MAX_PATH];
1537 WCHAR output[MAX_PATH];
1538 WCHAR drive[10];
1539 WCHAR dir[MAX_PATH];
1540 WCHAR fname[MAX_PATH];
1541 WCHAR ext[MAX_PATH];
1543 if (param1[0] == 0x00) {
1544 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1545 return;
1548 /* If no destination supplied, assume current directory */
1549 if (param2[0] == 0x00) {
1550 strcpyW(param2, dotW);
1553 /* If 2nd parm is directory, then use original filename */
1554 /* Convert partial path to full path */
1555 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1556 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1557 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1558 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1560 /* Split into components */
1561 WCMD_splitpath(input, drive, dir, fname, ext);
1563 hff = FindFirstFileW(input, &fd);
1564 while (hff != INVALID_HANDLE_VALUE) {
1565 WCHAR dest[MAX_PATH];
1566 WCHAR src[MAX_PATH];
1567 DWORD attribs;
1569 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1571 /* Build src & dest name */
1572 strcpyW(src, drive);
1573 strcatW(src, dir);
1575 /* See if dest is an existing directory */
1576 attribs = GetFileAttributesW(output);
1577 if (attribs != INVALID_FILE_ATTRIBUTES &&
1578 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1579 strcpyW(dest, output);
1580 strcatW(dest, slashW);
1581 strcatW(dest, fd.cFileName);
1582 } else {
1583 strcpyW(dest, output);
1586 strcatW(src, fd.cFileName);
1588 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1589 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1591 /* Check if file is read only, otherwise move it */
1592 attribs = GetFileAttributesW(src);
1593 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1594 (attribs & FILE_ATTRIBUTE_READONLY)) {
1595 SetLastError(ERROR_ACCESS_DENIED);
1596 status = 0;
1597 } else {
1598 BOOL ok = TRUE;
1600 /* If destination exists, prompt unless /Y supplied */
1601 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1602 BOOL force = FALSE;
1603 WCHAR copycmd[MAXSTRING];
1604 int len;
1606 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1607 if (strstrW (quals, parmNoY))
1608 force = FALSE;
1609 else if (strstrW (quals, parmY))
1610 force = TRUE;
1611 else {
1612 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1613 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1614 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1615 && ! lstrcmpiW (copycmd, parmY));
1618 /* Prompt if overwriting */
1619 if (!force) {
1620 WCHAR question[MAXSTRING];
1621 WCHAR yesChar[10];
1623 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1625 /* Ask for confirmation */
1626 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1627 ok = WCMD_ask_confirm(question, FALSE, NULL);
1629 /* So delete the destination prior to the move */
1630 if (ok) {
1631 if (!DeleteFileW(dest)) {
1632 WCMD_print_error ();
1633 errorlevel = 1;
1634 ok = FALSE;
1640 if (ok) {
1641 status = MoveFileW(src, dest);
1642 } else {
1643 status = 1; /* Anything other than 0 to prevent error msg below */
1647 if (!status) {
1648 WCMD_print_error ();
1649 errorlevel = 1;
1652 /* Step on to next match */
1653 if (FindNextFileW(hff, &fd) == 0) {
1654 FindClose(hff);
1655 hff = INVALID_HANDLE_VALUE;
1656 break;
1661 /****************************************************************************
1662 * WCMD_pause
1664 * Wait for keyboard input.
1667 void WCMD_pause (void) {
1669 DWORD count;
1670 WCHAR string[32];
1672 WCMD_output (anykey);
1673 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1674 sizeof(string)/sizeof(WCHAR), &count, NULL);
1677 /****************************************************************************
1678 * WCMD_remove_dir
1680 * Delete a directory.
1683 void WCMD_remove_dir (WCHAR *command) {
1685 int argno = 0;
1686 int argsProcessed = 0;
1687 WCHAR *argN = command;
1688 static const WCHAR parmS[] = {'/','S','\0'};
1689 static const WCHAR parmQ[] = {'/','Q','\0'};
1691 /* Loop through all args */
1692 while (argN) {
1693 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1694 if (argN && argN[0] != '/') {
1695 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1696 wine_dbgstr_w(quals));
1697 argsProcessed++;
1699 /* If subdirectory search not supplied, just try to remove
1700 and report error if it fails (eg if it contains a file) */
1701 if (strstrW (quals, parmS) == NULL) {
1702 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1704 /* Otherwise use ShFileOp to recursively remove a directory */
1705 } else {
1707 SHFILEOPSTRUCTW lpDir;
1709 /* Ask first */
1710 if (strstrW (quals, parmQ) == NULL) {
1711 BOOL ok;
1712 WCHAR question[MAXSTRING];
1713 static const WCHAR fmt[] = {'%','s',' ','\0'};
1715 /* Ask for confirmation */
1716 wsprintfW(question, fmt, thisArg);
1717 ok = WCMD_ask_confirm(question, TRUE, NULL);
1719 /* Abort if answer is 'N' */
1720 if (!ok) return;
1723 /* Do the delete */
1724 lpDir.hwnd = NULL;
1725 lpDir.pTo = NULL;
1726 lpDir.pFrom = thisArg;
1727 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1728 lpDir.wFunc = FO_DELETE;
1729 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1734 /* Handle no valid args */
1735 if (argsProcessed == 0) {
1736 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1737 return;
1742 /****************************************************************************
1743 * WCMD_rename
1745 * Rename a file.
1748 void WCMD_rename (void) {
1750 int status;
1751 HANDLE hff;
1752 WIN32_FIND_DATAW fd;
1753 WCHAR input[MAX_PATH];
1754 WCHAR *dotDst = NULL;
1755 WCHAR drive[10];
1756 WCHAR dir[MAX_PATH];
1757 WCHAR fname[MAX_PATH];
1758 WCHAR ext[MAX_PATH];
1759 DWORD attribs;
1761 errorlevel = 0;
1763 /* Must be at least two args */
1764 if (param1[0] == 0x00 || param2[0] == 0x00) {
1765 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1766 errorlevel = 1;
1767 return;
1770 /* Destination cannot contain a drive letter or directory separator */
1771 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1772 SetLastError(ERROR_INVALID_PARAMETER);
1773 WCMD_print_error();
1774 errorlevel = 1;
1775 return;
1778 /* Convert partial path to full path */
1779 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1780 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1781 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1782 dotDst = strchrW(param2, '.');
1784 /* Split into components */
1785 WCMD_splitpath(input, drive, dir, fname, ext);
1787 hff = FindFirstFileW(input, &fd);
1788 while (hff != INVALID_HANDLE_VALUE) {
1789 WCHAR dest[MAX_PATH];
1790 WCHAR src[MAX_PATH];
1791 WCHAR *dotSrc = NULL;
1792 int dirLen;
1794 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1796 /* FIXME: If dest name or extension is *, replace with filename/ext
1797 part otherwise use supplied name. This supports:
1798 ren *.fred *.jim
1799 ren jim.* fred.* etc
1800 However, windows has a more complex algorithm supporting eg
1801 ?'s and *'s mid name */
1802 dotSrc = strchrW(fd.cFileName, '.');
1804 /* Build src & dest name */
1805 strcpyW(src, drive);
1806 strcatW(src, dir);
1807 strcpyW(dest, src);
1808 dirLen = strlenW(src);
1809 strcatW(src, fd.cFileName);
1811 /* Build name */
1812 if (param2[0] == '*') {
1813 strcatW(dest, fd.cFileName);
1814 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1815 } else {
1816 strcatW(dest, param2);
1817 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1820 /* Build Extension */
1821 if (dotDst && (*(dotDst+1)=='*')) {
1822 if (dotSrc) strcatW(dest, dotSrc);
1823 } else if (dotDst) {
1824 if (dotDst) strcatW(dest, dotDst);
1827 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1828 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1830 /* Check if file is read only, otherwise move it */
1831 attribs = GetFileAttributesW(src);
1832 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1833 (attribs & FILE_ATTRIBUTE_READONLY)) {
1834 SetLastError(ERROR_ACCESS_DENIED);
1835 status = 0;
1836 } else {
1837 status = MoveFileW(src, dest);
1840 if (!status) {
1841 WCMD_print_error ();
1842 errorlevel = 1;
1845 /* Step on to next match */
1846 if (FindNextFileW(hff, &fd) == 0) {
1847 FindClose(hff);
1848 hff = INVALID_HANDLE_VALUE;
1849 break;
1854 /*****************************************************************************
1855 * WCMD_dupenv
1857 * Make a copy of the environment.
1859 static WCHAR *WCMD_dupenv( const WCHAR *env )
1861 WCHAR *env_copy;
1862 int len;
1864 if( !env )
1865 return NULL;
1867 len = 0;
1868 while ( env[len] )
1869 len += (strlenW(&env[len]) + 1);
1871 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1872 if (!env_copy)
1874 WINE_ERR("out of memory\n");
1875 return env_copy;
1877 memcpy (env_copy, env, len*sizeof (WCHAR));
1878 env_copy[len] = 0;
1880 return env_copy;
1883 /*****************************************************************************
1884 * WCMD_setlocal
1886 * setlocal pushes the environment onto a stack
1887 * Save the environment as unicode so we don't screw anything up.
1889 void WCMD_setlocal (const WCHAR *s) {
1890 WCHAR *env;
1891 struct env_stack *env_copy;
1892 WCHAR cwd[MAX_PATH];
1894 /* DISABLEEXTENSIONS ignored */
1896 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1897 if( !env_copy )
1899 WINE_ERR ("out of memory\n");
1900 return;
1903 env = GetEnvironmentStringsW ();
1905 env_copy->strings = WCMD_dupenv (env);
1906 if (env_copy->strings)
1908 env_copy->next = saved_environment;
1909 saved_environment = env_copy;
1911 /* Save the current drive letter */
1912 GetCurrentDirectoryW(MAX_PATH, cwd);
1913 env_copy->u.cwd = cwd[0];
1915 else
1916 LocalFree (env_copy);
1918 FreeEnvironmentStringsW (env);
1922 /*****************************************************************************
1923 * WCMD_endlocal
1925 * endlocal pops the environment off a stack
1926 * Note: When searching for '=', search from WCHAR position 1, to handle
1927 * special internal environment variables =C:, =D: etc
1929 void WCMD_endlocal (void) {
1930 WCHAR *env, *old, *p;
1931 struct env_stack *temp;
1932 int len, n;
1934 if (!saved_environment)
1935 return;
1937 /* pop the old environment from the stack */
1938 temp = saved_environment;
1939 saved_environment = temp->next;
1941 /* delete the current environment, totally */
1942 env = GetEnvironmentStringsW ();
1943 old = WCMD_dupenv (GetEnvironmentStringsW ());
1944 len = 0;
1945 while (old[len]) {
1946 n = strlenW(&old[len]) + 1;
1947 p = strchrW(&old[len] + 1, '=');
1948 if (p)
1950 *p++ = 0;
1951 SetEnvironmentVariableW (&old[len], NULL);
1953 len += n;
1955 LocalFree (old);
1956 FreeEnvironmentStringsW (env);
1958 /* restore old environment */
1959 env = temp->strings;
1960 len = 0;
1961 while (env[len]) {
1962 n = strlenW(&env[len]) + 1;
1963 p = strchrW(&env[len] + 1, '=');
1964 if (p)
1966 *p++ = 0;
1967 SetEnvironmentVariableW (&env[len], p);
1969 len += n;
1972 /* Restore current drive letter */
1973 if (IsCharAlphaW(temp->u.cwd)) {
1974 WCHAR envvar[4];
1975 WCHAR cwd[MAX_PATH];
1976 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1978 wsprintfW(envvar, fmt, temp->u.cwd);
1979 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1980 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1981 SetCurrentDirectoryW(cwd);
1985 LocalFree (env);
1986 LocalFree (temp);
1989 /*****************************************************************************
1990 * WCMD_setshow_attrib
1992 * Display and optionally sets DOS attributes on a file or directory
1996 void WCMD_setshow_attrib (void) {
1998 DWORD count;
1999 HANDLE hff;
2000 WIN32_FIND_DATAW fd;
2001 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
2002 WCHAR *name = param1;
2003 DWORD attrib_set=0;
2004 DWORD attrib_clear=0;
2006 if (param1[0] == '+' || param1[0] == '-') {
2007 DWORD attrib = 0;
2008 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
2009 switch (param1[1]) {
2010 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
2011 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
2012 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
2013 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
2014 default:
2015 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2016 return;
2018 switch (param1[0]) {
2019 case '+': attrib_set = attrib; break;
2020 case '-': attrib_clear = attrib; break;
2022 name = param2;
2025 if (strlenW(name) == 0) {
2026 static const WCHAR slashStarW[] = {'\\','*','\0'};
2028 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
2029 strcatW (name, slashStarW);
2032 hff = FindFirstFileW(name, &fd);
2033 if (hff == INVALID_HANDLE_VALUE) {
2034 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
2036 else {
2037 do {
2038 if (attrib_set || attrib_clear) {
2039 fd.dwFileAttributes &= ~attrib_clear;
2040 fd.dwFileAttributes |= attrib_set;
2041 if (!fd.dwFileAttributes)
2042 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
2043 SetFileAttributesW(name, fd.dwFileAttributes);
2044 } else {
2045 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
2046 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
2047 flags[0] = 'H';
2049 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
2050 flags[1] = 'S';
2052 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
2053 flags[2] = 'A';
2055 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
2056 flags[3] = 'R';
2058 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
2059 flags[4] = 'T';
2061 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
2062 flags[5] = 'C';
2064 WCMD_output (fmt, flags, fd.cFileName);
2065 for (count=0; count < 8; count++) flags[count] = ' ';
2067 } while (FindNextFileW(hff, &fd) != 0);
2069 FindClose (hff);
2072 /*****************************************************************************
2073 * WCMD_setshow_default
2075 * Set/Show the current default directory
2078 void WCMD_setshow_default (WCHAR *command) {
2080 BOOL status;
2081 WCHAR string[1024];
2082 WCHAR cwd[1024];
2083 WCHAR *pos;
2084 WIN32_FIND_DATAW fd;
2085 HANDLE hff;
2086 static const WCHAR parmD[] = {'/','D','\0'};
2088 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2090 /* Skip /D and trailing whitespace if on the front of the command line */
2091 if (CompareStringW(LOCALE_USER_DEFAULT,
2092 NORM_IGNORECASE | SORT_STRINGSORT,
2093 command, 2, parmD, -1) == 2) {
2094 command += 2;
2095 while (*command && *command==' ') command++;
2098 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2099 if (strlenW(command) == 0) {
2100 strcatW (cwd, newline);
2101 WCMD_output (cwd);
2103 else {
2104 /* Remove any double quotes, which may be in the
2105 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2106 pos = string;
2107 while (*command) {
2108 if (*command != '"') *pos++ = *command;
2109 command++;
2111 *pos = 0x00;
2113 /* Search for appropriate directory */
2114 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2115 hff = FindFirstFileW(string, &fd);
2116 while (hff != INVALID_HANDLE_VALUE) {
2117 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2118 WCHAR fpath[MAX_PATH];
2119 WCHAR drive[10];
2120 WCHAR dir[MAX_PATH];
2121 WCHAR fname[MAX_PATH];
2122 WCHAR ext[MAX_PATH];
2123 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2125 /* Convert path into actual directory spec */
2126 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2127 WCMD_splitpath(fpath, drive, dir, fname, ext);
2129 /* Rebuild path */
2130 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2132 FindClose(hff);
2133 hff = INVALID_HANDLE_VALUE;
2134 break;
2137 /* Step on to next match */
2138 if (FindNextFileW(hff, &fd) == 0) {
2139 FindClose(hff);
2140 hff = INVALID_HANDLE_VALUE;
2141 break;
2145 /* Change to that directory */
2146 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2148 status = SetCurrentDirectoryW(string);
2149 if (!status) {
2150 errorlevel = 1;
2151 WCMD_print_error ();
2152 return;
2153 } else {
2155 /* Save away the actual new directory, to store as current location */
2156 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2158 /* Restore old directory if drive letter would change, and
2159 CD x:\directory /D (or pushd c:\directory) not supplied */
2160 if ((strstrW(quals, parmD) == NULL) &&
2161 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2162 SetCurrentDirectoryW(cwd);
2166 /* Set special =C: type environment variable, for drive letter of
2167 change of directory, even if path was restored due to missing
2168 /D (allows changing drive letter when not resident on that
2169 drive */
2170 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2171 WCHAR env[4];
2172 strcpyW(env, equalW);
2173 memcpy(env+1, string, 2 * sizeof(WCHAR));
2174 env[3] = 0x00;
2175 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2176 SetEnvironmentVariableW(env, string);
2180 return;
2183 /****************************************************************************
2184 * WCMD_setshow_date
2186 * Set/Show the system date
2187 * FIXME: Can't change date yet
2190 void WCMD_setshow_date (void) {
2192 WCHAR curdate[64], buffer[64];
2193 DWORD count;
2194 static const WCHAR parmT[] = {'/','T','\0'};
2196 if (strlenW(param1) == 0) {
2197 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2198 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2199 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2200 if (strstrW (quals, parmT) == NULL) {
2201 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2202 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2203 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2204 if (count > 2) {
2205 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2209 else WCMD_print_error ();
2211 else {
2212 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2216 /****************************************************************************
2217 * WCMD_compare
2219 static int WCMD_compare( const void *a, const void *b )
2221 int r;
2222 const WCHAR * const *str_a = a, * const *str_b = b;
2223 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2224 *str_a, -1, *str_b, -1 );
2225 if( r == CSTR_LESS_THAN ) return -1;
2226 if( r == CSTR_GREATER_THAN ) return 1;
2227 return 0;
2230 /****************************************************************************
2231 * WCMD_setshow_sortenv
2233 * sort variables into order for display
2234 * Optionally only display those who start with a stub
2235 * returns the count displayed
2237 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2239 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2240 const WCHAR **str;
2242 if (stub) stublen = strlenW(stub);
2244 /* count the number of strings, and the total length */
2245 while ( s[len] ) {
2246 len += (strlenW(&s[len]) + 1);
2247 count++;
2250 /* add the strings to an array */
2251 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2252 if( !str )
2253 return 0;
2254 str[0] = s;
2255 for( i=1; i<count; i++ )
2256 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2258 /* sort the array */
2259 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2261 /* print it */
2262 for( i=0; i<count; i++ ) {
2263 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2264 NORM_IGNORECASE | SORT_STRINGSORT,
2265 str[i], stublen, stub, -1) == 2) {
2266 /* Don't display special internal variables */
2267 if (str[i][0] != '=') {
2268 WCMD_output_asis(str[i]);
2269 WCMD_output_asis(newline);
2270 displayedcount++;
2275 LocalFree( str );
2276 return displayedcount;
2279 /****************************************************************************
2280 * WCMD_setshow_env
2282 * Set/Show the environment variables
2285 void WCMD_setshow_env (WCHAR *s) {
2287 LPVOID env;
2288 WCHAR *p;
2289 int status;
2290 static const WCHAR parmP[] = {'/','P','\0'};
2292 if (param1[0] == 0x00 && quals[0] == 0x00) {
2293 env = GetEnvironmentStringsW();
2294 WCMD_setshow_sortenv( env, NULL );
2295 return;
2298 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2299 if (CompareStringW(LOCALE_USER_DEFAULT,
2300 NORM_IGNORECASE | SORT_STRINGSORT,
2301 s, 2, parmP, -1) == 2) {
2302 WCHAR string[MAXSTRING];
2303 DWORD count;
2305 s += 2;
2306 while (*s && *s==' ') s++;
2307 if (*s=='\"')
2308 WCMD_opt_s_strip_quotes(s);
2310 /* If no parameter, or no '=' sign, return an error */
2311 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2312 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2313 return;
2316 /* Output the prompt */
2317 *p++ = '\0';
2318 if (strlenW(p) != 0) WCMD_output(p);
2320 /* Read the reply */
2321 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2322 sizeof(string)/sizeof(WCHAR), &count, NULL);
2323 if (count > 1) {
2324 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2325 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2326 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2327 wine_dbgstr_w(string));
2328 status = SetEnvironmentVariableW(s, string);
2331 } else {
2332 DWORD gle;
2334 if (*s=='\"')
2335 WCMD_opt_s_strip_quotes(s);
2336 p = strchrW (s, '=');
2337 if (p == NULL) {
2338 env = GetEnvironmentStringsW();
2339 if (WCMD_setshow_sortenv( env, s ) == 0) {
2340 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2341 errorlevel = 1;
2343 return;
2345 *p++ = '\0';
2347 if (strlenW(p) == 0) p = NULL;
2348 status = SetEnvironmentVariableW(s, p);
2349 gle = GetLastError();
2350 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2351 errorlevel = 1;
2352 } else if ((!status)) WCMD_print_error();
2356 /****************************************************************************
2357 * WCMD_setshow_path
2359 * Set/Show the path environment variable
2362 void WCMD_setshow_path (WCHAR *command) {
2364 WCHAR string[1024];
2365 DWORD status;
2366 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2367 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2369 if (strlenW(param1) == 0) {
2370 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2371 if (status != 0) {
2372 WCMD_output_asis ( pathEqW);
2373 WCMD_output_asis ( string);
2374 WCMD_output_asis ( newline);
2376 else {
2377 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2380 else {
2381 if (*command == '=') command++; /* Skip leading '=' */
2382 status = SetEnvironmentVariableW(pathW, command);
2383 if (!status) WCMD_print_error();
2387 /****************************************************************************
2388 * WCMD_setshow_prompt
2390 * Set or show the command prompt.
2393 void WCMD_setshow_prompt (void) {
2395 WCHAR *s;
2396 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2398 if (strlenW(param1) == 0) {
2399 SetEnvironmentVariableW(promptW, NULL);
2401 else {
2402 s = param1;
2403 while ((*s == '=') || (*s == ' ')) s++;
2404 if (strlenW(s) == 0) {
2405 SetEnvironmentVariableW(promptW, NULL);
2407 else SetEnvironmentVariableW(promptW, s);
2411 /****************************************************************************
2412 * WCMD_setshow_time
2414 * Set/Show the system time
2415 * FIXME: Can't change time yet
2418 void WCMD_setshow_time (void) {
2420 WCHAR curtime[64], buffer[64];
2421 DWORD count;
2422 SYSTEMTIME st;
2423 static const WCHAR parmT[] = {'/','T','\0'};
2425 if (strlenW(param1) == 0) {
2426 GetLocalTime(&st);
2427 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2428 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2429 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2430 if (strstrW (quals, parmT) == NULL) {
2431 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2432 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2433 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2434 if (count > 2) {
2435 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2439 else WCMD_print_error ();
2441 else {
2442 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2446 /****************************************************************************
2447 * WCMD_shift
2449 * Shift batch parameters.
2450 * Optional /n says where to start shifting (n=0-8)
2453 void WCMD_shift (WCHAR *command) {
2454 int start;
2456 if (context != NULL) {
2457 WCHAR *pos = strchrW(command, '/');
2458 int i;
2460 if (pos == NULL) {
2461 start = 0;
2462 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2463 start = (*(pos+1) - '0');
2464 } else {
2465 SetLastError(ERROR_INVALID_PARAMETER);
2466 WCMD_print_error();
2467 return;
2470 WINE_TRACE("Shifting variables, starting at %d\n", start);
2471 for (i=start;i<=8;i++) {
2472 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2474 context -> shift_count[9] = context -> shift_count[9] + 1;
2479 /****************************************************************************
2480 * WCMD_title
2482 * Set the console title
2484 void WCMD_title (WCHAR *command) {
2485 SetConsoleTitleW(command);
2488 /****************************************************************************
2489 * WCMD_type
2491 * Copy a file to standard output.
2494 void WCMD_type (WCHAR *command) {
2496 int argno = 0;
2497 WCHAR *argN = command;
2498 BOOL writeHeaders = FALSE;
2500 if (param1[0] == 0x00) {
2501 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2502 return;
2505 if (param2[0] != 0x00) writeHeaders = TRUE;
2507 /* Loop through all args */
2508 errorlevel = 0;
2509 while (argN) {
2510 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2512 HANDLE h;
2513 WCHAR buffer[512];
2514 DWORD count;
2516 if (!argN) break;
2518 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2519 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2520 FILE_ATTRIBUTE_NORMAL, NULL);
2521 if (h == INVALID_HANDLE_VALUE) {
2522 WCMD_print_error ();
2523 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2524 errorlevel = 1;
2525 } else {
2526 if (writeHeaders) {
2527 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2528 WCMD_output(fmt, thisArg);
2530 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2531 if (count == 0) break; /* ReadFile reports success on EOF! */
2532 buffer[count] = 0;
2533 WCMD_output_asis (buffer);
2535 CloseHandle (h);
2540 /****************************************************************************
2541 * WCMD_more
2543 * Output either a file or stdin to screen in pages
2546 void WCMD_more (WCHAR *command) {
2548 int argno = 0;
2549 WCHAR *argN = command;
2550 WCHAR moreStr[100];
2551 WCHAR moreStrPage[100];
2552 WCHAR buffer[512];
2553 DWORD count;
2554 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2555 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2556 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2557 ')',' ','-','-','\n','\0'};
2558 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2560 /* Prefix the NLS more with '-- ', then load the text */
2561 errorlevel = 0;
2562 strcpyW(moreStr, moreStart);
2563 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2564 (sizeof(moreStr)/sizeof(WCHAR))-3);
2566 if (param1[0] == 0x00) {
2568 /* Wine implements pipes via temporary files, and hence stdin is
2569 effectively reading from the file. This means the prompts for
2570 more are satisfied by the next line from the input (file). To
2571 avoid this, ensure stdin is to the console */
2572 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2573 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2574 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2575 FILE_ATTRIBUTE_NORMAL, 0);
2576 WINE_TRACE("No parms - working probably in pipe mode\n");
2577 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2579 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2580 once you get in this bit unless due to a pipe, its going to end badly... */
2581 wsprintfW(moreStrPage, moreFmt, moreStr);
2583 WCMD_enter_paged_mode(moreStrPage);
2584 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2585 if (count == 0) break; /* ReadFile reports success on EOF! */
2586 buffer[count] = 0;
2587 WCMD_output_asis (buffer);
2589 WCMD_leave_paged_mode();
2591 /* Restore stdin to what it was */
2592 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2593 CloseHandle(hConIn);
2595 return;
2596 } else {
2597 BOOL needsPause = FALSE;
2599 /* Loop through all args */
2600 WINE_TRACE("Parms supplied - working through each file\n");
2601 WCMD_enter_paged_mode(moreStrPage);
2603 while (argN) {
2604 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2605 HANDLE h;
2607 if (!argN) break;
2609 if (needsPause) {
2611 /* Wait */
2612 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2613 WCMD_leave_paged_mode();
2614 WCMD_output_asis(moreStrPage);
2615 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2616 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2617 WCMD_enter_paged_mode(moreStrPage);
2621 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2622 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2623 FILE_ATTRIBUTE_NORMAL, NULL);
2624 if (h == INVALID_HANDLE_VALUE) {
2625 WCMD_print_error ();
2626 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2627 errorlevel = 1;
2628 } else {
2629 ULONG64 curPos = 0;
2630 ULONG64 fileLen = 0;
2631 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2633 /* Get the file size */
2634 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2635 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2637 needsPause = TRUE;
2638 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2639 if (count == 0) break; /* ReadFile reports success on EOF! */
2640 buffer[count] = 0;
2641 curPos += count;
2643 /* Update % count (would be used in WCMD_output_asis as prompt) */
2644 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2646 WCMD_output_asis (buffer);
2648 CloseHandle (h);
2652 WCMD_leave_paged_mode();
2656 /****************************************************************************
2657 * WCMD_verify
2659 * Display verify flag.
2660 * FIXME: We don't actually do anything with the verify flag other than toggle
2661 * it...
2664 void WCMD_verify (WCHAR *command) {
2666 int count;
2668 count = strlenW(command);
2669 if (count == 0) {
2670 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2671 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2672 return;
2674 if (lstrcmpiW(command, onW) == 0) {
2675 verify_mode = 1;
2676 return;
2678 else if (lstrcmpiW(command, offW) == 0) {
2679 verify_mode = 0;
2680 return;
2682 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2685 /****************************************************************************
2686 * WCMD_version
2688 * Display version info.
2691 void WCMD_version (void) {
2693 WCMD_output (version_string);
2697 /****************************************************************************
2698 * WCMD_volume
2700 * Display volume info and/or set volume label. Returns 0 if error.
2703 int WCMD_volume (int mode, WCHAR *path) {
2705 DWORD count, serial;
2706 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2707 BOOL status;
2709 if (strlenW(path) == 0) {
2710 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2711 if (!status) {
2712 WCMD_print_error ();
2713 return 0;
2715 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2716 &serial, NULL, NULL, NULL, 0);
2718 else {
2719 static const WCHAR fmt[] = {'%','s','\\','\0'};
2720 if ((path[1] != ':') || (strlenW(path) != 2)) {
2721 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2722 return 0;
2724 wsprintfW (curdir, fmt, path);
2725 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2726 &serial, NULL,
2727 NULL, NULL, 0);
2729 if (!status) {
2730 WCMD_print_error ();
2731 return 0;
2733 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2734 curdir[0], label, HIWORD(serial), LOWORD(serial));
2735 if (mode) {
2736 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2737 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2738 sizeof(string)/sizeof(WCHAR), &count, NULL);
2739 if (count > 1) {
2740 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2741 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2743 if (strlenW(path) != 0) {
2744 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2746 else {
2747 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2750 return 1;
2753 /**************************************************************************
2754 * WCMD_exit
2756 * Exit either the process, or just this batch program
2760 void WCMD_exit (CMD_LIST **cmdList) {
2762 static const WCHAR parmB[] = {'/','B','\0'};
2763 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2765 if (context && lstrcmpiW(quals, parmB) == 0) {
2766 errorlevel = rc;
2767 context -> skip_rest = TRUE;
2768 *cmdList = NULL;
2769 } else {
2770 ExitProcess(rc);
2775 /*****************************************************************************
2776 * WCMD_assoc
2778 * Lists or sets file associations (assoc = TRUE)
2779 * Lists or sets file types (assoc = FALSE)
2781 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2783 HKEY key;
2784 DWORD accessOptions = KEY_READ;
2785 WCHAR *newValue;
2786 LONG rc = ERROR_SUCCESS;
2787 WCHAR keyValue[MAXSTRING];
2788 DWORD valueLen = MAXSTRING;
2789 HKEY readKey;
2790 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2791 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2793 /* See if parameter includes '=' */
2794 errorlevel = 0;
2795 newValue = strchrW(command, '=');
2796 if (newValue) accessOptions |= KEY_WRITE;
2798 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2799 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2800 accessOptions, &key) != ERROR_SUCCESS) {
2801 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2802 return;
2805 /* If no parameters then list all associations */
2806 if (*command == 0x00) {
2807 int index = 0;
2809 /* Enumerate all the keys */
2810 while (rc != ERROR_NO_MORE_ITEMS) {
2811 WCHAR keyName[MAXSTRING];
2812 DWORD nameLen;
2814 /* Find the next value */
2815 nameLen = MAXSTRING;
2816 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2818 if (rc == ERROR_SUCCESS) {
2820 /* Only interested in extension ones if assoc, or others
2821 if not assoc */
2822 if ((keyName[0] == '.' && assoc) ||
2823 (!(keyName[0] == '.') && (!assoc)))
2825 WCHAR subkey[MAXSTRING];
2826 strcpyW(subkey, keyName);
2827 if (!assoc) strcatW(subkey, shOpCmdW);
2829 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2831 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2832 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2833 WCMD_output_asis(keyName);
2834 WCMD_output_asis(equalW);
2835 /* If no default value found, leave line empty after '=' */
2836 if (rc == ERROR_SUCCESS) {
2837 WCMD_output_asis(keyValue);
2839 WCMD_output_asis(newline);
2840 RegCloseKey(readKey);
2846 } else {
2848 /* Parameter supplied - if no '=' on command line, its a query */
2849 if (newValue == NULL) {
2850 WCHAR *space;
2851 WCHAR subkey[MAXSTRING];
2853 /* Query terminates the parameter at the first space */
2854 strcpyW(keyValue, command);
2855 space = strchrW(keyValue, ' ');
2856 if (space) *space=0x00;
2858 /* Set up key name */
2859 strcpyW(subkey, keyValue);
2860 if (!assoc) strcatW(subkey, shOpCmdW);
2862 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2864 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2865 WCMD_output_asis(command);
2866 WCMD_output_asis(equalW);
2867 /* If no default value found, leave line empty after '=' */
2868 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2869 WCMD_output_asis(newline);
2870 RegCloseKey(readKey);
2872 } else {
2873 WCHAR msgbuffer[MAXSTRING];
2874 WCHAR outbuffer[MAXSTRING];
2876 /* Load the translated 'File association not found' */
2877 if (assoc) {
2878 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2879 } else {
2880 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2882 wsprintfW(outbuffer, msgbuffer, keyValue);
2883 WCMD_output_asis(outbuffer);
2884 errorlevel = 2;
2887 /* Not a query - its a set or clear of a value */
2888 } else {
2890 WCHAR subkey[MAXSTRING];
2892 /* Get pointer to new value */
2893 *newValue = 0x00;
2894 newValue++;
2896 /* Set up key name */
2897 strcpyW(subkey, command);
2898 if (!assoc) strcatW(subkey, shOpCmdW);
2900 /* If nothing after '=' then clear value - only valid for ASSOC */
2901 if (*newValue == 0x00) {
2903 if (assoc) rc = RegDeleteKeyW(key, command);
2904 if (assoc && rc == ERROR_SUCCESS) {
2905 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2907 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2908 WCMD_print_error();
2909 errorlevel = 2;
2911 } else {
2912 WCHAR msgbuffer[MAXSTRING];
2913 WCHAR outbuffer[MAXSTRING];
2915 /* Load the translated 'File association not found' */
2916 if (assoc) {
2917 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2918 sizeof(msgbuffer)/sizeof(WCHAR));
2919 } else {
2920 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2921 sizeof(msgbuffer)/sizeof(WCHAR));
2923 wsprintfW(outbuffer, msgbuffer, keyValue);
2924 WCMD_output_asis(outbuffer);
2925 errorlevel = 2;
2928 /* It really is a set value = contents */
2929 } else {
2930 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2931 accessOptions, NULL, &readKey, NULL);
2932 if (rc == ERROR_SUCCESS) {
2933 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2934 (LPBYTE)newValue, strlenW(newValue));
2935 RegCloseKey(readKey);
2938 if (rc != ERROR_SUCCESS) {
2939 WCMD_print_error();
2940 errorlevel = 2;
2941 } else {
2942 WCMD_output_asis(command);
2943 WCMD_output_asis(equalW);
2944 WCMD_output_asis(newValue);
2945 WCMD_output_asis(newline);
2951 /* Clean up */
2952 RegCloseKey(key);
2955 /****************************************************************************
2956 * WCMD_color
2958 * Clear the terminal screen.
2961 void WCMD_color (void) {
2963 /* Emulate by filling the screen from the top left to bottom right with
2964 spaces, then moving the cursor to the top left afterwards */
2965 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2966 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2968 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2969 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2970 return;
2973 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2975 COORD topLeft;
2976 DWORD screenSize;
2977 DWORD color = 0;
2979 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2981 topLeft.X = 0;
2982 topLeft.Y = 0;
2984 /* Convert the color hex digits */
2985 if (param1[0] == 0x00) {
2986 color = defaultColor;
2987 } else {
2988 color = strtoulW(param1, NULL, 16);
2991 /* Fail if fg == bg color */
2992 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2993 errorlevel = 1;
2994 return;
2997 /* Set the current screen contents and ensure all future writes
2998 remain this color */
2999 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3000 SetConsoleTextAttribute(hStdOut, color);