winedbg: Add the Serbian (Cyrillic) translation.
[wine/multimedia.git] / programs / cmd / builtins.c
blob5ccbb8fecdd25388d2ffa27197f309496b9409f0
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_copy
165 * Copy a file or wildcarded set.
166 * FIXME: Add support for a+b+c type syntax
169 void WCMD_copy (void) {
171 WIN32_FIND_DATAW fd;
172 HANDLE hff;
173 BOOL force, status;
174 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
175 DWORD len;
176 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
177 BOOL copyToDir = FALSE;
178 WCHAR srcspec[MAX_PATH];
179 DWORD attribs;
180 WCHAR drive[10];
181 WCHAR dir[MAX_PATH];
182 WCHAR fname[MAX_PATH];
183 WCHAR ext[MAX_PATH];
185 if (param1[0] == 0x00) {
186 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
187 return;
190 /* Convert source into full spec */
191 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
192 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
193 if (srcpath[strlenW(srcpath) - 1] == '\\')
194 srcpath[strlenW(srcpath) - 1] = '\0';
196 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
197 attribs = GetFileAttributesW(srcpath);
198 } else {
199 attribs = 0;
201 strcpyW(srcspec, srcpath);
203 /* If a directory, then add \* on the end when searching */
204 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
205 strcatW(srcpath, slashW);
206 strcatW(srcspec, slashW);
207 strcatW(srcspec, starW);
208 } else {
209 WCMD_splitpath(srcpath, drive, dir, fname, ext);
210 strcpyW(srcpath, drive);
211 strcatW(srcpath, dir);
214 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
216 /* If no destination supplied, assume current directory */
217 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
218 if (param2[0] == 0x00) {
219 strcpyW(param2, dotW);
222 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
223 if (outpath[strlenW(outpath) - 1] == '\\')
224 outpath[strlenW(outpath) - 1] = '\0';
225 attribs = GetFileAttributesW(outpath);
226 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
227 strcatW (outpath, slashW);
228 copyToDir = TRUE;
230 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
231 wine_dbgstr_w(outpath), copyToDir);
233 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
234 if (strstrW (quals, parmNoY))
235 force = FALSE;
236 else if (strstrW (quals, parmY))
237 force = TRUE;
238 else {
239 /* By default, we will force the overwrite in batch mode and ask for
240 * confirmation in interactive mode. */
241 force = !!context;
243 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
244 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
245 * default behavior. */
246 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
247 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
248 if (!lstrcmpiW (copycmd, parmY))
249 force = TRUE;
250 else if (!lstrcmpiW (copycmd, parmNoY))
251 force = FALSE;
255 /* Loop through all source files */
256 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
257 hff = FindFirstFileW(srcspec, &fd);
258 if (hff != INVALID_HANDLE_VALUE) {
259 do {
260 WCHAR outname[MAX_PATH];
261 WCHAR srcname[MAX_PATH];
262 BOOL overwrite = force;
264 /* Destination is either supplied filename, or source name in
265 supplied destination directory */
266 strcpyW(outname, outpath);
267 if (copyToDir) strcatW(outname, fd.cFileName);
268 strcpyW(srcname, srcpath);
269 strcatW(srcname, fd.cFileName);
271 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
272 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
274 /* Skip . and .., and directories */
275 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
276 overwrite = FALSE;
277 WINE_TRACE("Skipping directories\n");
280 /* Prompt before overwriting */
281 else if (!overwrite) {
282 attribs = GetFileAttributesW(outname);
283 if (attribs != INVALID_FILE_ATTRIBUTES) {
284 WCHAR buffer[MAXSTRING];
285 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
286 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
288 else overwrite = TRUE;
291 /* Do the copy as appropriate */
292 if (overwrite) {
293 status = CopyFileW(srcname, outname, FALSE);
294 if (!status) WCMD_print_error ();
297 } while (FindNextFileW(hff, &fd) != 0);
298 FindClose (hff);
299 } else {
300 status = ERROR_FILE_NOT_FOUND;
301 WCMD_print_error ();
305 /****************************************************************************
306 * WCMD_create_dir
308 * Create a directory.
310 * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
311 * they do not already exist.
314 static BOOL create_full_path(WCHAR* path)
316 int len;
317 WCHAR *new_path;
318 BOOL ret = TRUE;
320 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path)+1) * sizeof(WCHAR));
321 strcpyW(new_path,path);
323 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
324 new_path[len - 1] = 0;
326 while (!CreateDirectoryW(new_path,NULL))
328 WCHAR *slash;
329 DWORD last_error = GetLastError();
330 if (last_error == ERROR_ALREADY_EXISTS)
331 break;
333 if (last_error != ERROR_PATH_NOT_FOUND)
335 ret = FALSE;
336 break;
339 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
341 ret = FALSE;
342 break;
345 len = slash - new_path;
346 new_path[len] = 0;
347 if (!create_full_path(new_path))
349 ret = FALSE;
350 break;
352 new_path[len] = '\\';
354 HeapFree(GetProcessHeap(),0,new_path);
355 return ret;
358 void WCMD_create_dir (void) {
360 if (param1[0] == 0x00) {
361 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
362 return;
364 if (!create_full_path(param1)) WCMD_print_error ();
367 /****************************************************************************
368 * WCMD_delete
370 * Delete a file or wildcarded set.
372 * Note on /A:
373 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
374 * - Each set is a pattern, eg /ahr /as-r means
375 * readonly+hidden OR nonreadonly system files
376 * - The '-' applies to a single field, ie /a:-hr means read only
377 * non-hidden files
380 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
382 int argno = 0;
383 int argsProcessed = 0;
384 WCHAR *argN = command;
385 BOOL foundAny = FALSE;
386 static const WCHAR parmA[] = {'/','A','\0'};
387 static const WCHAR parmQ[] = {'/','Q','\0'};
388 static const WCHAR parmP[] = {'/','P','\0'};
389 static const WCHAR parmS[] = {'/','S','\0'};
390 static const WCHAR parmF[] = {'/','F','\0'};
392 /* If not recursing, clear error flag */
393 if (expectDir) errorlevel = 0;
395 /* Loop through all args */
396 while (argN) {
397 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
398 WCHAR argCopy[MAX_PATH];
400 if (argN && argN[0] != '/') {
402 WIN32_FIND_DATAW fd;
403 HANDLE hff;
404 WCHAR fpath[MAX_PATH];
405 WCHAR *p;
406 BOOL handleParm = TRUE;
407 BOOL found = FALSE;
408 static const WCHAR anyExt[]= {'.','*','\0'};
410 strcpyW(argCopy, thisArg);
411 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
412 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
413 argsProcessed++;
415 /* If filename part of parameter is * or *.*, prompt unless
416 /Q supplied. */
417 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
419 WCHAR drive[10];
420 WCHAR dir[MAX_PATH];
421 WCHAR fname[MAX_PATH];
422 WCHAR ext[MAX_PATH];
424 /* Convert path into actual directory spec */
425 GetFullPathNameW(argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
426 WCMD_splitpath(fpath, drive, dir, fname, ext);
428 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
429 if ((strcmpW(fname, starW) == 0) &&
430 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
431 BOOL ok;
432 WCHAR question[MAXSTRING];
433 static const WCHAR fmt[] = {'%','s',' ','\0'};
435 /* Note: Flag as found, to avoid file not found message */
436 found = TRUE;
438 /* Ask for confirmation */
439 wsprintfW(question, fmt, fpath);
440 ok = WCMD_ask_confirm(question, TRUE, NULL);
442 /* Abort if answer is 'N' */
443 if (!ok) continue;
447 /* First, try to delete in the current directory */
448 hff = FindFirstFileW(argCopy, &fd);
449 if (hff == INVALID_HANDLE_VALUE) {
450 handleParm = FALSE;
451 } else {
452 found = TRUE;
455 /* Support del <dirname> by just deleting all files dirname\* */
456 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
457 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
458 WCHAR modifiedParm[MAX_PATH];
459 static const WCHAR slashStar[] = {'\\','*','\0'};
461 strcpyW(modifiedParm, argCopy);
462 strcatW(modifiedParm, slashStar);
463 FindClose(hff);
464 found = TRUE;
465 WCMD_delete(modifiedParm, FALSE);
467 } else if (handleParm) {
469 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
470 strcpyW (fpath, argCopy);
471 do {
472 p = strrchrW (fpath, '\\');
473 if (p != NULL) {
474 *++p = '\0';
475 strcatW (fpath, fd.cFileName);
477 else strcpyW (fpath, fd.cFileName);
478 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
479 BOOL ok = TRUE;
480 WCHAR *nextA = strstrW (quals, parmA);
482 /* Handle attribute matching (/A) */
483 if (nextA != NULL) {
484 ok = FALSE;
485 while (nextA != NULL && !ok) {
487 WCHAR *thisA = (nextA+2);
488 BOOL stillOK = TRUE;
490 /* Skip optional : */
491 if (*thisA == ':') thisA++;
493 /* Parse each of the /A[:]xxx in turn */
494 while (*thisA && *thisA != '/') {
495 BOOL negate = FALSE;
496 BOOL attribute = FALSE;
498 /* Match negation of attribute first */
499 if (*thisA == '-') {
500 negate=TRUE;
501 thisA++;
504 /* Match attribute */
505 switch (*thisA) {
506 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
507 break;
508 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
509 break;
510 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
511 break;
512 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
513 break;
514 default:
515 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
518 /* Now check result, keeping a running boolean about whether it
519 matches all parsed attributes so far */
520 if (attribute && !negate) {
521 stillOK = stillOK;
522 } else if (!attribute && negate) {
523 stillOK = stillOK;
524 } else {
525 stillOK = FALSE;
527 thisA++;
530 /* Save the running total as the final result */
531 ok = stillOK;
533 /* Step on to next /A set */
534 nextA = strstrW (nextA+1, parmA);
538 /* /P means prompt for each file */
539 if (ok && strstrW (quals, parmP) != NULL) {
540 WCHAR question[MAXSTRING];
542 /* Ask for confirmation */
543 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
544 ok = WCMD_ask_confirm(question, FALSE, NULL);
547 /* Only proceed if ok to */
548 if (ok) {
550 /* If file is read only, and /F supplied, delete it */
551 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
552 strstrW (quals, parmF) != NULL) {
553 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
556 /* Now do the delete */
557 if (!DeleteFileW(fpath)) WCMD_print_error ();
561 } while (FindNextFileW(hff, &fd) != 0);
562 FindClose (hff);
565 /* Now recurse into all subdirectories handling the parameter in the same way */
566 if (strstrW (quals, parmS) != NULL) {
568 WCHAR thisDir[MAX_PATH];
569 int cPos;
571 WCHAR drive[10];
572 WCHAR dir[MAX_PATH];
573 WCHAR fname[MAX_PATH];
574 WCHAR ext[MAX_PATH];
576 /* Convert path into actual directory spec */
577 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
578 WCMD_splitpath(thisDir, drive, dir, fname, ext);
580 strcpyW(thisDir, drive);
581 strcatW(thisDir, dir);
582 cPos = strlenW(thisDir);
584 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
586 /* Append '*' to the directory */
587 thisDir[cPos] = '*';
588 thisDir[cPos+1] = 0x00;
590 hff = FindFirstFileW(thisDir, &fd);
592 /* Remove residual '*' */
593 thisDir[cPos] = 0x00;
595 if (hff != INVALID_HANDLE_VALUE) {
596 DIRECTORY_STACK *allDirs = NULL;
597 DIRECTORY_STACK *lastEntry = NULL;
599 do {
600 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
601 (strcmpW(fd.cFileName, dotdotW) != 0) &&
602 (strcmpW(fd.cFileName, dotW) != 0)) {
604 DIRECTORY_STACK *nextDir;
605 WCHAR subParm[MAX_PATH];
607 /* Work out search parameter in sub dir */
608 strcpyW (subParm, thisDir);
609 strcatW (subParm, fd.cFileName);
610 strcatW (subParm, slashW);
611 strcatW (subParm, fname);
612 strcatW (subParm, ext);
613 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
615 /* Allocate memory, add to list */
616 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
617 if (allDirs == NULL) allDirs = nextDir;
618 if (lastEntry != NULL) lastEntry->next = nextDir;
619 lastEntry = nextDir;
620 nextDir->next = NULL;
621 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
622 (strlenW(subParm)+1) * sizeof(WCHAR));
623 strcpyW(nextDir->dirName, subParm);
625 } while (FindNextFileW(hff, &fd) != 0);
626 FindClose (hff);
628 /* Go through each subdir doing the delete */
629 while (allDirs != NULL) {
630 DIRECTORY_STACK *tempDir;
632 tempDir = allDirs->next;
633 found |= WCMD_delete (allDirs->dirName, FALSE);
635 HeapFree(GetProcessHeap(),0,allDirs->dirName);
636 HeapFree(GetProcessHeap(),0,allDirs);
637 allDirs = tempDir;
641 /* Keep running total to see if any found, and if not recursing
642 issue error message */
643 if (expectDir) {
644 if (!found) {
645 errorlevel = 1;
646 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
649 foundAny |= found;
653 /* Handle no valid args */
654 if (argsProcessed == 0) {
655 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
658 return foundAny;
661 /****************************************************************************
662 * WCMD_echo
664 * Echo input to the screen (or not). We don't try to emulate the bugs
665 * in DOS (try typing "ECHO ON AGAIN" for an example).
668 void WCMD_echo (const WCHAR *command) {
670 int count;
671 const WCHAR *origcommand = command;
673 if (command[0]==' ' || command[0]=='.')
674 command++;
675 count = strlenW(command);
676 if (count == 0 && origcommand[0]!='.') {
677 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
678 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
679 return;
681 if (lstrcmpiW(command, onW) == 0) {
682 echo_mode = 1;
683 return;
685 if (lstrcmpiW(command, offW) == 0) {
686 echo_mode = 0;
687 return;
689 WCMD_output_asis (command);
690 WCMD_output (newline);
694 /**************************************************************************
695 * WCMD_for
697 * Batch file loop processing.
699 * On entry: cmdList contains the syntax up to the set
700 * next cmdList and all in that bracket contain the set data
701 * next cmdlist contains the DO cmd
702 * following that is either brackets or && entries (as per if)
706 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
708 WIN32_FIND_DATAW fd;
709 HANDLE hff;
710 int i;
711 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
712 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
713 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
714 WCHAR variable[4];
715 WCHAR *firstCmd;
716 int thisDepth;
718 WCHAR *curPos = p;
719 BOOL expandDirs = FALSE;
720 BOOL useNumbers = FALSE;
721 BOOL doFileset = FALSE;
722 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
723 int itemNum;
724 CMD_LIST *thisCmdStart;
727 /* Handle optional qualifiers (multiple are allowed) */
728 while (*curPos && *curPos == '/') {
729 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
730 curPos++;
731 switch (toupperW(*curPos)) {
732 case 'D': curPos++; expandDirs = TRUE; break;
733 case 'L': curPos++; useNumbers = TRUE; break;
735 /* Recursive is special case - /R can have an optional path following it */
736 /* filenamesets are another special case - /F can have an optional options following it */
737 case 'R':
738 case 'F':
740 BOOL isRecursive = (*curPos == 'R');
742 if (!isRecursive)
743 doFileset = TRUE;
745 /* Skip whitespace */
746 curPos++;
747 while (*curPos && *curPos==' ') curPos++;
749 /* Next parm is either qualifier, path/options or variable -
750 only care about it if it is the path/options */
751 if (*curPos && *curPos != '/' && *curPos != '%') {
752 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
753 else WINE_FIXME("/F needs to handle options\n");
755 break;
757 default:
758 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
759 curPos++;
762 /* Skip whitespace between qualifiers */
763 while (*curPos && *curPos==' ') curPos++;
766 /* Skip whitespace before variable */
767 while (*curPos && *curPos==' ') curPos++;
769 /* Ensure line continues with variable */
770 if (!*curPos || *curPos != '%') {
771 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
772 return;
775 /* Variable should follow */
776 i = 0;
777 while (curPos[i] && curPos[i]!=' ') i++;
778 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
779 variable[i] = 0x00;
780 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
781 curPos = &curPos[i];
783 /* Skip whitespace before IN */
784 while (*curPos && *curPos==' ') curPos++;
786 /* Ensure line continues with IN */
787 if (!*curPos || lstrcmpiW (curPos, inW)) {
788 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
789 return;
792 /* Save away where the set of data starts and the variable */
793 thisDepth = (*cmdList)->bracketDepth;
794 *cmdList = (*cmdList)->nextcommand;
795 setStart = (*cmdList);
797 /* Skip until the close bracket */
798 WINE_TRACE("Searching %p as the set\n", *cmdList);
799 while (*cmdList &&
800 (*cmdList)->command != NULL &&
801 (*cmdList)->bracketDepth > thisDepth) {
802 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
803 *cmdList = (*cmdList)->nextcommand;
806 /* Skip the close bracket, if there is one */
807 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
809 /* Syntax error if missing close bracket, or nothing following it
810 and once we have the complete set, we expect a DO */
811 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
812 if ((*cmdList == NULL) ||
813 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
814 (*cmdList)->command, 3, doW, -1) != 2)) {
815 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
816 return;
819 /* Save away the starting position for the commands (and offset for the
820 first one */
821 cmdStart = *cmdList;
822 cmdEnd = *cmdList;
823 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
824 itemNum = 0;
826 thisSet = setStart;
827 /* Loop through all set entries */
828 while (thisSet &&
829 thisSet->command != NULL &&
830 thisSet->bracketDepth >= thisDepth) {
832 /* Loop through all entries on the same line */
833 WCHAR *item;
834 WCHAR *itemStart;
836 WINE_TRACE("Processing for set %p\n", thisSet);
837 i = 0;
838 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
841 * If the parameter within the set has a wildcard then search for matching files
842 * otherwise do a literal substitution.
844 static const WCHAR wildcards[] = {'*','?','\0'};
845 thisCmdStart = cmdStart;
847 itemNum++;
848 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
850 if (!useNumbers && !doFileset) {
851 if (strpbrkW (item, wildcards)) {
852 hff = FindFirstFileW(item, &fd);
853 if (hff != INVALID_HANDLE_VALUE) {
854 do {
855 BOOL isDirectory = FALSE;
857 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
859 /* Handle as files or dirs appropriately, but ignore . and .. */
860 if (isDirectory == expandDirs &&
861 (strcmpW(fd.cFileName, dotdotW) != 0) &&
862 (strcmpW(fd.cFileName, dotW) != 0))
864 thisCmdStart = cmdStart;
865 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
866 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
867 fd.cFileName, FALSE, TRUE);
870 } while (FindNextFileW(hff, &fd) != 0);
871 FindClose (hff);
873 } else {
874 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
877 } else if (useNumbers) {
878 /* Convert the first 3 numbers to signed longs and save */
879 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
880 /* else ignore them! */
882 /* Filesets - either a list of files, or a command to run and parse the output */
883 } else if (doFileset && *itemStart != '"') {
885 HANDLE input;
886 WCHAR temp_file[MAX_PATH];
888 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
889 wine_dbgstr_w(item));
891 /* If backquote or single quote, we need to launch that command
892 and parse the results - use a temporary file */
893 if (*itemStart == '`' || *itemStart == '\'') {
895 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
896 static const WCHAR redirOut[] = {'>','%','s','\0'};
897 static const WCHAR cmdW[] = {'C','M','D','\0'};
899 /* Remove trailing character */
900 itemStart[strlenW(itemStart)-1] = 0x00;
902 /* Get temp filename */
903 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
904 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
906 /* Execute program and redirect output */
907 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
908 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
910 /* Open the file, read line by line and process */
911 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
912 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
913 } else {
915 /* Open the file, read line by line and process */
916 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
917 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
920 /* Process the input file */
921 if (input == INVALID_HANDLE_VALUE) {
922 WCMD_print_error ();
923 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
924 errorlevel = 1;
925 return; /* FOR loop aborts at first failure here */
927 } else {
929 WCHAR buffer[MAXSTRING] = {'\0'};
930 WCHAR *where, *parm;
932 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
934 /* Skip blank lines*/
935 parm = WCMD_parameter (buffer, 0, &where);
936 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
937 wine_dbgstr_w(buffer));
939 if (where) {
940 /* FIXME: The following should be moved into its own routine and
941 reused for the string literal parsing below */
942 thisCmdStart = cmdStart;
943 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
944 cmdEnd = thisCmdStart;
947 buffer[0] = 0x00;
950 CloseHandle (input);
953 /* Delete the temporary file */
954 if (*itemStart == '`' || *itemStart == '\'') {
955 DeleteFileW(temp_file);
958 /* Filesets - A string literal */
959 } else if (doFileset && *itemStart == '"') {
960 WCHAR buffer[MAXSTRING] = {'\0'};
961 WCHAR *where, *parm;
963 /* Skip blank lines, and re-extract parameter now string has quotes removed */
964 strcpyW(buffer, item);
965 parm = WCMD_parameter (buffer, 0, &where);
966 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
967 wine_dbgstr_w(buffer));
969 if (where) {
970 /* FIXME: The following should be moved into its own routine and
971 reused for the string literal parsing below */
972 thisCmdStart = cmdStart;
973 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
974 cmdEnd = thisCmdStart;
978 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
979 cmdEnd = thisCmdStart;
980 i++;
983 /* Move onto the next set line */
984 thisSet = thisSet->nextcommand;
987 /* If /L is provided, now run the for loop */
988 if (useNumbers) {
989 WCHAR thisNum[20];
990 static const WCHAR fmt[] = {'%','d','\0'};
992 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
993 numbers[0], numbers[2], numbers[1]);
994 for (i=numbers[0];
995 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
996 i=i + numbers[1]) {
998 sprintfW(thisNum, fmt, i);
999 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1001 thisCmdStart = cmdStart;
1002 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1003 cmdEnd = thisCmdStart;
1007 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1008 all processing, OR it should be pointing to the end of && processing OR
1009 it should be pointing at the NULL end of bracket for the DO. The return
1010 value needs to be the NEXT command to execute, which it either is, or
1011 we need to step over the closing bracket */
1012 *cmdList = cmdEnd;
1013 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1017 /*****************************************************************************
1018 * WCMD_part_execute
1020 * Execute a command, and any && or bracketed follow on to the command. The
1021 * first command to be executed may not be at the front of the
1022 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1024 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1025 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1027 CMD_LIST *curPosition = *cmdList;
1028 int myDepth = (*cmdList)->bracketDepth;
1030 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1031 cmdList, wine_dbgstr_w(firstcmd),
1032 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1033 conditionTRUE);
1035 /* Skip leading whitespace between condition and the command */
1036 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1038 /* Process the first command, if there is one */
1039 if (conditionTRUE && firstcmd && *firstcmd) {
1040 WCHAR *command = WCMD_strdupW(firstcmd);
1041 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1042 HeapFree(GetProcessHeap(), 0, command);
1046 /* If it didn't move the position, step to next command */
1047 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1049 /* Process any other parts of the command */
1050 if (*cmdList) {
1051 BOOL processThese = TRUE;
1053 if (isIF) processThese = conditionTRUE;
1055 while (*cmdList) {
1056 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1058 /* execute all appropriate commands */
1059 curPosition = *cmdList;
1061 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1062 *cmdList,
1063 (*cmdList)->prevDelim,
1064 (*cmdList)->bracketDepth, myDepth);
1066 /* Execute any statements appended to the line */
1067 /* FIXME: Only if previous call worked for && or failed for || */
1068 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1069 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1070 if (processThese && (*cmdList)->command) {
1071 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1072 value, cmdList);
1074 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1076 /* Execute any appended to the statement with (...) */
1077 } else if ((*cmdList)->bracketDepth > myDepth) {
1078 if (processThese) {
1079 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1080 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1082 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1084 /* End of the command - does 'ELSE ' follow as the next command? */
1085 } else {
1086 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1087 NORM_IGNORECASE | SORT_STRINGSORT,
1088 (*cmdList)->command, 5, ifElse, -1) == 2) {
1090 /* Swap between if and else processing */
1091 processThese = !processThese;
1093 /* Process the ELSE part */
1094 if (processThese) {
1095 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1097 /* Skip leading whitespace between condition and the command */
1098 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1099 if (*cmd) {
1100 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1103 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1104 } else {
1105 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1106 break;
1111 return;
1114 /**************************************************************************
1115 * WCMD_give_help
1117 * Simple on-line help. Help text is stored in the resource file.
1120 void WCMD_give_help (WCHAR *command) {
1122 int i;
1124 command = WCMD_strtrim_leading_spaces(command);
1125 if (strlenW(command) == 0) {
1126 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1128 else {
1129 for (i=0; i<=WCMD_EXIT; i++) {
1130 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1131 command, -1, inbuilt[i], -1) == 2) {
1132 WCMD_output_asis (WCMD_LoadMessage(i));
1133 return;
1136 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1138 return;
1141 /****************************************************************************
1142 * WCMD_go_to
1144 * Batch file jump instruction. Not the most efficient algorithm ;-)
1145 * Prints error message if the specified label cannot be found - the file pointer is
1146 * then at EOF, effectively stopping the batch file.
1147 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1150 void WCMD_goto (CMD_LIST **cmdList) {
1152 WCHAR string[MAX_PATH];
1154 /* Do not process any more parts of a processed multipart or multilines command */
1155 if (cmdList) *cmdList = NULL;
1157 if (param1[0] == 0x00) {
1158 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1159 return;
1161 if (context != NULL) {
1162 WCHAR *paramStart = param1, *str;
1163 static const WCHAR eofW[] = {':','e','o','f','\0'};
1165 /* Handle special :EOF label */
1166 if (lstrcmpiW (eofW, param1) == 0) {
1167 context -> skip_rest = TRUE;
1168 return;
1171 /* Support goto :label as well as goto label */
1172 if (*paramStart == ':') paramStart++;
1174 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1175 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1176 str = string;
1177 while (isspaceW(*str)) str++;
1178 if ((*str == ':') && (lstrcmpiW (++str, paramStart) == 0)) return;
1180 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1182 return;
1185 /*****************************************************************************
1186 * WCMD_pushd
1188 * Push a directory onto the stack
1191 void WCMD_pushd (WCHAR *command) {
1192 struct env_stack *curdir;
1193 WCHAR *thisdir;
1194 static const WCHAR parmD[] = {'/','D','\0'};
1196 if (strchrW(command, '/') != NULL) {
1197 SetLastError(ERROR_INVALID_PARAMETER);
1198 WCMD_print_error();
1199 return;
1202 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1203 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1204 if( !curdir || !thisdir ) {
1205 LocalFree(curdir);
1206 LocalFree(thisdir);
1207 WINE_ERR ("out of memory\n");
1208 return;
1211 /* Change directory using CD code with /D parameter */
1212 strcpyW(quals, parmD);
1213 GetCurrentDirectoryW (1024, thisdir);
1214 errorlevel = 0;
1215 WCMD_setshow_default(command);
1216 if (errorlevel) {
1217 LocalFree(curdir);
1218 LocalFree(thisdir);
1219 return;
1220 } else {
1221 curdir -> next = pushd_directories;
1222 curdir -> strings = thisdir;
1223 if (pushd_directories == NULL) {
1224 curdir -> u.stackdepth = 1;
1225 } else {
1226 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1228 pushd_directories = curdir;
1233 /*****************************************************************************
1234 * WCMD_popd
1236 * Pop a directory from the stack
1239 void WCMD_popd (void) {
1240 struct env_stack *temp = pushd_directories;
1242 if (!pushd_directories)
1243 return;
1245 /* pop the old environment from the stack, and make it the current dir */
1246 pushd_directories = temp->next;
1247 SetCurrentDirectoryW(temp->strings);
1248 LocalFree (temp->strings);
1249 LocalFree (temp);
1252 /****************************************************************************
1253 * WCMD_if
1255 * Batch file conditional.
1257 * On entry, cmdlist will point to command containing the IF, and optionally
1258 * the first command to execute (if brackets not found)
1259 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1260 * If ('s were found, execute all within that bracket
1261 * Command may optionally be followed by an ELSE - need to skip instructions
1262 * in the else using the same logic
1264 * FIXME: Much more syntax checking needed!
1267 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1269 int negate = 0, test = 0;
1270 WCHAR condition[MAX_PATH], *command, *s;
1271 static const WCHAR notW[] = {'n','o','t','\0'};
1272 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1273 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1274 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1275 static const WCHAR eqeqW[] = {'=','=','\0'};
1276 static const WCHAR parmI[] = {'/','I','\0'};
1278 if (!lstrcmpiW (param1, notW)) {
1279 negate = 1;
1280 strcpyW (condition, param2);
1282 else {
1283 strcpyW (condition, param1);
1285 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1287 if (!lstrcmpiW (condition, errlvlW)) {
1288 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1289 WCMD_parameter (p, 2+negate, &command);
1291 else if (!lstrcmpiW (condition, existW)) {
1292 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1293 test = 1;
1295 WCMD_parameter (p, 2+negate, &command);
1297 else if (!lstrcmpiW (condition, defdW)) {
1298 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1299 test = 1;
1301 WCMD_parameter (p, 2+negate, &command);
1303 else if ((s = strstrW (p, eqeqW))) {
1304 s += 2;
1305 if (strstrW (quals, parmI) == NULL) {
1306 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1308 else {
1309 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1311 WCMD_parameter (s, 1, &command);
1313 else {
1314 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1315 return;
1318 /* Process rest of IF statement which is on the same line
1319 Note: This may process all or some of the cmdList (eg a GOTO) */
1320 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1323 /****************************************************************************
1324 * WCMD_move
1326 * Move a file, directory tree or wildcarded set of files.
1329 void WCMD_move (void) {
1331 int status;
1332 WIN32_FIND_DATAW fd;
1333 HANDLE hff;
1334 WCHAR input[MAX_PATH];
1335 WCHAR output[MAX_PATH];
1336 WCHAR drive[10];
1337 WCHAR dir[MAX_PATH];
1338 WCHAR fname[MAX_PATH];
1339 WCHAR ext[MAX_PATH];
1341 if (param1[0] == 0x00) {
1342 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1343 return;
1346 /* If no destination supplied, assume current directory */
1347 if (param2[0] == 0x00) {
1348 strcpyW(param2, dotW);
1351 /* If 2nd parm is directory, then use original filename */
1352 /* Convert partial path to full path */
1353 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1354 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1355 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1356 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1358 /* Split into components */
1359 WCMD_splitpath(input, drive, dir, fname, ext);
1361 hff = FindFirstFileW(input, &fd);
1362 while (hff != INVALID_HANDLE_VALUE) {
1363 WCHAR dest[MAX_PATH];
1364 WCHAR src[MAX_PATH];
1365 DWORD attribs;
1367 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1369 /* Build src & dest name */
1370 strcpyW(src, drive);
1371 strcatW(src, dir);
1373 /* See if dest is an existing directory */
1374 attribs = GetFileAttributesW(output);
1375 if (attribs != INVALID_FILE_ATTRIBUTES &&
1376 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1377 strcpyW(dest, output);
1378 strcatW(dest, slashW);
1379 strcatW(dest, fd.cFileName);
1380 } else {
1381 strcpyW(dest, output);
1384 strcatW(src, fd.cFileName);
1386 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1387 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1389 /* Check if file is read only, otherwise move it */
1390 attribs = GetFileAttributesW(src);
1391 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1392 (attribs & FILE_ATTRIBUTE_READONLY)) {
1393 SetLastError(ERROR_ACCESS_DENIED);
1394 status = 0;
1395 } else {
1396 BOOL ok = TRUE;
1398 /* If destination exists, prompt unless /Y supplied */
1399 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1400 BOOL force = FALSE;
1401 WCHAR copycmd[MAXSTRING];
1402 int len;
1404 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1405 if (strstrW (quals, parmNoY))
1406 force = FALSE;
1407 else if (strstrW (quals, parmY))
1408 force = TRUE;
1409 else {
1410 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1411 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1412 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1413 && ! lstrcmpiW (copycmd, parmY));
1416 /* Prompt if overwriting */
1417 if (!force) {
1418 WCHAR question[MAXSTRING];
1419 WCHAR yesChar[10];
1421 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1423 /* Ask for confirmation */
1424 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1425 ok = WCMD_ask_confirm(question, FALSE, NULL);
1427 /* So delete the destination prior to the move */
1428 if (ok) {
1429 if (!DeleteFileW(dest)) {
1430 WCMD_print_error ();
1431 errorlevel = 1;
1432 ok = FALSE;
1438 if (ok) {
1439 status = MoveFileW(src, dest);
1440 } else {
1441 status = 1; /* Anything other than 0 to prevent error msg below */
1445 if (!status) {
1446 WCMD_print_error ();
1447 errorlevel = 1;
1450 /* Step on to next match */
1451 if (FindNextFileW(hff, &fd) == 0) {
1452 FindClose(hff);
1453 hff = INVALID_HANDLE_VALUE;
1454 break;
1459 /****************************************************************************
1460 * WCMD_pause
1462 * Wait for keyboard input.
1465 void WCMD_pause (void) {
1467 DWORD count;
1468 WCHAR string[32];
1470 WCMD_output (anykey);
1471 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1472 sizeof(string)/sizeof(WCHAR), &count, NULL);
1475 /****************************************************************************
1476 * WCMD_remove_dir
1478 * Delete a directory.
1481 void WCMD_remove_dir (WCHAR *command) {
1483 int argno = 0;
1484 int argsProcessed = 0;
1485 WCHAR *argN = command;
1486 static const WCHAR parmS[] = {'/','S','\0'};
1487 static const WCHAR parmQ[] = {'/','Q','\0'};
1489 /* Loop through all args */
1490 while (argN) {
1491 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1492 if (argN && argN[0] != '/') {
1493 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1494 wine_dbgstr_w(quals));
1495 argsProcessed++;
1497 /* If subdirectory search not supplied, just try to remove
1498 and report error if it fails (eg if it contains a file) */
1499 if (strstrW (quals, parmS) == NULL) {
1500 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1502 /* Otherwise use ShFileOp to recursively remove a directory */
1503 } else {
1505 SHFILEOPSTRUCTW lpDir;
1507 /* Ask first */
1508 if (strstrW (quals, parmQ) == NULL) {
1509 BOOL ok;
1510 WCHAR question[MAXSTRING];
1511 static const WCHAR fmt[] = {'%','s',' ','\0'};
1513 /* Ask for confirmation */
1514 wsprintfW(question, fmt, thisArg);
1515 ok = WCMD_ask_confirm(question, TRUE, NULL);
1517 /* Abort if answer is 'N' */
1518 if (!ok) return;
1521 /* Do the delete */
1522 lpDir.hwnd = NULL;
1523 lpDir.pTo = NULL;
1524 lpDir.pFrom = thisArg;
1525 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1526 lpDir.wFunc = FO_DELETE;
1527 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1532 /* Handle no valid args */
1533 if (argsProcessed == 0) {
1534 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1535 return;
1540 /****************************************************************************
1541 * WCMD_rename
1543 * Rename a file.
1546 void WCMD_rename (void) {
1548 int status;
1549 HANDLE hff;
1550 WIN32_FIND_DATAW fd;
1551 WCHAR input[MAX_PATH];
1552 WCHAR *dotDst = NULL;
1553 WCHAR drive[10];
1554 WCHAR dir[MAX_PATH];
1555 WCHAR fname[MAX_PATH];
1556 WCHAR ext[MAX_PATH];
1557 DWORD attribs;
1559 errorlevel = 0;
1561 /* Must be at least two args */
1562 if (param1[0] == 0x00 || param2[0] == 0x00) {
1563 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1564 errorlevel = 1;
1565 return;
1568 /* Destination cannot contain a drive letter or directory separator */
1569 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1570 SetLastError(ERROR_INVALID_PARAMETER);
1571 WCMD_print_error();
1572 errorlevel = 1;
1573 return;
1576 /* Convert partial path to full path */
1577 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1578 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1579 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1580 dotDst = strchrW(param2, '.');
1582 /* Split into components */
1583 WCMD_splitpath(input, drive, dir, fname, ext);
1585 hff = FindFirstFileW(input, &fd);
1586 while (hff != INVALID_HANDLE_VALUE) {
1587 WCHAR dest[MAX_PATH];
1588 WCHAR src[MAX_PATH];
1589 WCHAR *dotSrc = NULL;
1590 int dirLen;
1592 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1594 /* FIXME: If dest name or extension is *, replace with filename/ext
1595 part otherwise use supplied name. This supports:
1596 ren *.fred *.jim
1597 ren jim.* fred.* etc
1598 However, windows has a more complex algorithm supporting eg
1599 ?'s and *'s mid name */
1600 dotSrc = strchrW(fd.cFileName, '.');
1602 /* Build src & dest name */
1603 strcpyW(src, drive);
1604 strcatW(src, dir);
1605 strcpyW(dest, src);
1606 dirLen = strlenW(src);
1607 strcatW(src, fd.cFileName);
1609 /* Build name */
1610 if (param2[0] == '*') {
1611 strcatW(dest, fd.cFileName);
1612 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1613 } else {
1614 strcatW(dest, param2);
1615 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1618 /* Build Extension */
1619 if (dotDst && (*(dotDst+1)=='*')) {
1620 if (dotSrc) strcatW(dest, dotSrc);
1621 } else if (dotDst) {
1622 if (dotDst) strcatW(dest, dotDst);
1625 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1626 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1628 /* Check if file is read only, otherwise move it */
1629 attribs = GetFileAttributesW(src);
1630 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1631 (attribs & FILE_ATTRIBUTE_READONLY)) {
1632 SetLastError(ERROR_ACCESS_DENIED);
1633 status = 0;
1634 } else {
1635 status = MoveFileW(src, dest);
1638 if (!status) {
1639 WCMD_print_error ();
1640 errorlevel = 1;
1643 /* Step on to next match */
1644 if (FindNextFileW(hff, &fd) == 0) {
1645 FindClose(hff);
1646 hff = INVALID_HANDLE_VALUE;
1647 break;
1652 /*****************************************************************************
1653 * WCMD_dupenv
1655 * Make a copy of the environment.
1657 static WCHAR *WCMD_dupenv( const WCHAR *env )
1659 WCHAR *env_copy;
1660 int len;
1662 if( !env )
1663 return NULL;
1665 len = 0;
1666 while ( env[len] )
1667 len += (strlenW(&env[len]) + 1);
1669 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1670 if (!env_copy)
1672 WINE_ERR("out of memory\n");
1673 return env_copy;
1675 memcpy (env_copy, env, len*sizeof (WCHAR));
1676 env_copy[len] = 0;
1678 return env_copy;
1681 /*****************************************************************************
1682 * WCMD_setlocal
1684 * setlocal pushes the environment onto a stack
1685 * Save the environment as unicode so we don't screw anything up.
1687 void WCMD_setlocal (const WCHAR *s) {
1688 WCHAR *env;
1689 struct env_stack *env_copy;
1690 WCHAR cwd[MAX_PATH];
1692 /* DISABLEEXTENSIONS ignored */
1694 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1695 if( !env_copy )
1697 WINE_ERR ("out of memory\n");
1698 return;
1701 env = GetEnvironmentStringsW ();
1703 env_copy->strings = WCMD_dupenv (env);
1704 if (env_copy->strings)
1706 env_copy->next = saved_environment;
1707 saved_environment = env_copy;
1709 /* Save the current drive letter */
1710 GetCurrentDirectoryW(MAX_PATH, cwd);
1711 env_copy->u.cwd = cwd[0];
1713 else
1714 LocalFree (env_copy);
1716 FreeEnvironmentStringsW (env);
1720 /*****************************************************************************
1721 * WCMD_endlocal
1723 * endlocal pops the environment off a stack
1724 * Note: When searching for '=', search from WCHAR position 1, to handle
1725 * special internal environment variables =C:, =D: etc
1727 void WCMD_endlocal (void) {
1728 WCHAR *env, *old, *p;
1729 struct env_stack *temp;
1730 int len, n;
1732 if (!saved_environment)
1733 return;
1735 /* pop the old environment from the stack */
1736 temp = saved_environment;
1737 saved_environment = temp->next;
1739 /* delete the current environment, totally */
1740 env = GetEnvironmentStringsW ();
1741 old = WCMD_dupenv (GetEnvironmentStringsW ());
1742 len = 0;
1743 while (old[len]) {
1744 n = strlenW(&old[len]) + 1;
1745 p = strchrW(&old[len] + 1, '=');
1746 if (p)
1748 *p++ = 0;
1749 SetEnvironmentVariableW (&old[len], NULL);
1751 len += n;
1753 LocalFree (old);
1754 FreeEnvironmentStringsW (env);
1756 /* restore old environment */
1757 env = temp->strings;
1758 len = 0;
1759 while (env[len]) {
1760 n = strlenW(&env[len]) + 1;
1761 p = strchrW(&env[len] + 1, '=');
1762 if (p)
1764 *p++ = 0;
1765 SetEnvironmentVariableW (&env[len], p);
1767 len += n;
1770 /* Restore current drive letter */
1771 if (IsCharAlphaW(temp->u.cwd)) {
1772 WCHAR envvar[4];
1773 WCHAR cwd[MAX_PATH];
1774 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1776 wsprintfW(envvar, fmt, temp->u.cwd);
1777 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1778 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1779 SetCurrentDirectoryW(cwd);
1783 LocalFree (env);
1784 LocalFree (temp);
1787 /*****************************************************************************
1788 * WCMD_setshow_attrib
1790 * Display and optionally sets DOS attributes on a file or directory
1794 void WCMD_setshow_attrib (void) {
1796 DWORD count;
1797 HANDLE hff;
1798 WIN32_FIND_DATAW fd;
1799 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1800 WCHAR *name = param1;
1801 DWORD attrib_set=0;
1802 DWORD attrib_clear=0;
1804 if (param1[0] == '+' || param1[0] == '-') {
1805 DWORD attrib = 0;
1806 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
1807 switch (param1[1]) {
1808 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
1809 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
1810 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
1811 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
1812 default:
1813 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1814 return;
1816 switch (param1[0]) {
1817 case '+': attrib_set = attrib; break;
1818 case '-': attrib_clear = attrib; break;
1820 name = param2;
1823 if (strlenW(name) == 0) {
1824 static const WCHAR slashStarW[] = {'\\','*','\0'};
1826 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
1827 strcatW (name, slashStarW);
1830 hff = FindFirstFileW(name, &fd);
1831 if (hff == INVALID_HANDLE_VALUE) {
1832 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
1834 else {
1835 do {
1836 if (attrib_set || attrib_clear) {
1837 fd.dwFileAttributes &= ~attrib_clear;
1838 fd.dwFileAttributes |= attrib_set;
1839 if (!fd.dwFileAttributes)
1840 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
1841 SetFileAttributesW(name, fd.dwFileAttributes);
1842 } else {
1843 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1844 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1845 flags[0] = 'H';
1847 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1848 flags[1] = 'S';
1850 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1851 flags[2] = 'A';
1853 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1854 flags[3] = 'R';
1856 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1857 flags[4] = 'T';
1859 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1860 flags[5] = 'C';
1862 WCMD_output (fmt, flags, fd.cFileName);
1863 for (count=0; count < 8; count++) flags[count] = ' ';
1865 } while (FindNextFileW(hff, &fd) != 0);
1867 FindClose (hff);
1870 /*****************************************************************************
1871 * WCMD_setshow_default
1873 * Set/Show the current default directory
1876 void WCMD_setshow_default (WCHAR *command) {
1878 BOOL status;
1879 WCHAR string[1024];
1880 WCHAR cwd[1024];
1881 WCHAR *pos;
1882 WIN32_FIND_DATAW fd;
1883 HANDLE hff;
1884 static const WCHAR parmD[] = {'/','D','\0'};
1886 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1888 /* Skip /D and trailing whitespace if on the front of the command line */
1889 if (CompareStringW(LOCALE_USER_DEFAULT,
1890 NORM_IGNORECASE | SORT_STRINGSORT,
1891 command, 2, parmD, -1) == 2) {
1892 command += 2;
1893 while (*command && *command==' ') command++;
1896 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
1897 if (strlenW(command) == 0) {
1898 strcatW (cwd, newline);
1899 WCMD_output (cwd);
1901 else {
1902 /* Remove any double quotes, which may be in the
1903 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1904 pos = string;
1905 while (*command) {
1906 if (*command != '"') *pos++ = *command;
1907 command++;
1909 *pos = 0x00;
1911 /* Search for appropriate directory */
1912 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1913 hff = FindFirstFileW(string, &fd);
1914 while (hff != INVALID_HANDLE_VALUE) {
1915 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1916 WCHAR fpath[MAX_PATH];
1917 WCHAR drive[10];
1918 WCHAR dir[MAX_PATH];
1919 WCHAR fname[MAX_PATH];
1920 WCHAR ext[MAX_PATH];
1921 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1923 /* Convert path into actual directory spec */
1924 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1925 WCMD_splitpath(fpath, drive, dir, fname, ext);
1927 /* Rebuild path */
1928 wsprintfW(string, fmt, drive, dir, fd.cFileName);
1930 FindClose(hff);
1931 hff = INVALID_HANDLE_VALUE;
1932 break;
1935 /* Step on to next match */
1936 if (FindNextFileW(hff, &fd) == 0) {
1937 FindClose(hff);
1938 hff = INVALID_HANDLE_VALUE;
1939 break;
1943 /* Change to that directory */
1944 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1946 status = SetCurrentDirectoryW(string);
1947 if (!status) {
1948 errorlevel = 1;
1949 WCMD_print_error ();
1950 return;
1951 } else {
1953 /* Save away the actual new directory, to store as current location */
1954 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
1956 /* Restore old directory if drive letter would change, and
1957 CD x:\directory /D (or pushd c:\directory) not supplied */
1958 if ((strstrW(quals, parmD) == NULL) &&
1959 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1960 SetCurrentDirectoryW(cwd);
1964 /* Set special =C: type environment variable, for drive letter of
1965 change of directory, even if path was restored due to missing
1966 /D (allows changing drive letter when not resident on that
1967 drive */
1968 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
1969 WCHAR env[4];
1970 strcpyW(env, equalW);
1971 memcpy(env+1, string, 2 * sizeof(WCHAR));
1972 env[3] = 0x00;
1973 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1974 SetEnvironmentVariableW(env, string);
1978 return;
1981 /****************************************************************************
1982 * WCMD_setshow_date
1984 * Set/Show the system date
1985 * FIXME: Can't change date yet
1988 void WCMD_setshow_date (void) {
1990 WCHAR curdate[64], buffer[64];
1991 DWORD count;
1992 static const WCHAR parmT[] = {'/','T','\0'};
1994 if (strlenW(param1) == 0) {
1995 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
1996 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1997 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1998 if (strstrW (quals, parmT) == NULL) {
1999 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2000 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2001 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2002 if (count > 2) {
2003 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2007 else WCMD_print_error ();
2009 else {
2010 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2014 /****************************************************************************
2015 * WCMD_compare
2017 static int WCMD_compare( const void *a, const void *b )
2019 int r;
2020 const WCHAR * const *str_a = a, * const *str_b = b;
2021 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2022 *str_a, -1, *str_b, -1 );
2023 if( r == CSTR_LESS_THAN ) return -1;
2024 if( r == CSTR_GREATER_THAN ) return 1;
2025 return 0;
2028 /****************************************************************************
2029 * WCMD_setshow_sortenv
2031 * sort variables into order for display
2032 * Optionally only display those who start with a stub
2033 * returns the count displayed
2035 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2037 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2038 const WCHAR **str;
2040 if (stub) stublen = strlenW(stub);
2042 /* count the number of strings, and the total length */
2043 while ( s[len] ) {
2044 len += (strlenW(&s[len]) + 1);
2045 count++;
2048 /* add the strings to an array */
2049 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2050 if( !str )
2051 return 0;
2052 str[0] = s;
2053 for( i=1; i<count; i++ )
2054 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2056 /* sort the array */
2057 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2059 /* print it */
2060 for( i=0; i<count; i++ ) {
2061 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2062 NORM_IGNORECASE | SORT_STRINGSORT,
2063 str[i], stublen, stub, -1) == 2) {
2064 /* Don't display special internal variables */
2065 if (str[i][0] != '=') {
2066 WCMD_output_asis(str[i]);
2067 WCMD_output_asis(newline);
2068 displayedcount++;
2073 LocalFree( str );
2074 return displayedcount;
2077 /****************************************************************************
2078 * WCMD_setshow_env
2080 * Set/Show the environment variables
2083 void WCMD_setshow_env (WCHAR *s) {
2085 LPVOID env;
2086 WCHAR *p;
2087 int status;
2088 static const WCHAR parmP[] = {'/','P','\0'};
2090 errorlevel = 0;
2091 if (param1[0] == 0x00 && quals[0] == 0x00) {
2092 env = GetEnvironmentStringsW();
2093 WCMD_setshow_sortenv( env, NULL );
2094 return;
2097 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2098 if (CompareStringW(LOCALE_USER_DEFAULT,
2099 NORM_IGNORECASE | SORT_STRINGSORT,
2100 s, 2, parmP, -1) == 2) {
2101 WCHAR string[MAXSTRING];
2102 DWORD count;
2104 s += 2;
2105 while (*s && *s==' ') s++;
2106 if (*s=='\"')
2107 WCMD_opt_s_strip_quotes(s);
2109 /* If no parameter, or no '=' sign, return an error */
2110 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2111 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2112 return;
2115 /* Output the prompt */
2116 *p++ = '\0';
2117 if (strlenW(p) != 0) WCMD_output(p);
2119 /* Read the reply */
2120 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2121 sizeof(string)/sizeof(WCHAR), &count, NULL);
2122 if (count > 1) {
2123 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2124 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2125 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2126 wine_dbgstr_w(string));
2127 status = SetEnvironmentVariableW(s, string);
2130 } else {
2131 DWORD gle;
2133 if (*s=='\"')
2134 WCMD_opt_s_strip_quotes(s);
2135 p = strchrW (s, '=');
2136 if (p == NULL) {
2137 env = GetEnvironmentStringsW();
2138 if (WCMD_setshow_sortenv( env, s ) == 0) {
2139 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2140 errorlevel = 1;
2142 return;
2144 *p++ = '\0';
2146 if (strlenW(p) == 0) p = NULL;
2147 status = SetEnvironmentVariableW(s, p);
2148 gle = GetLastError();
2149 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2150 errorlevel = 1;
2151 } else if ((!status)) WCMD_print_error();
2155 /****************************************************************************
2156 * WCMD_setshow_path
2158 * Set/Show the path environment variable
2161 void WCMD_setshow_path (WCHAR *command) {
2163 WCHAR string[1024];
2164 DWORD status;
2165 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2166 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2168 if (strlenW(param1) == 0) {
2169 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2170 if (status != 0) {
2171 WCMD_output_asis ( pathEqW);
2172 WCMD_output_asis ( string);
2173 WCMD_output_asis ( newline);
2175 else {
2176 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2179 else {
2180 if (*command == '=') command++; /* Skip leading '=' */
2181 status = SetEnvironmentVariableW(pathW, command);
2182 if (!status) WCMD_print_error();
2186 /****************************************************************************
2187 * WCMD_setshow_prompt
2189 * Set or show the command prompt.
2192 void WCMD_setshow_prompt (void) {
2194 WCHAR *s;
2195 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2197 if (strlenW(param1) == 0) {
2198 SetEnvironmentVariableW(promptW, NULL);
2200 else {
2201 s = param1;
2202 while ((*s == '=') || (*s == ' ')) s++;
2203 if (strlenW(s) == 0) {
2204 SetEnvironmentVariableW(promptW, NULL);
2206 else SetEnvironmentVariableW(promptW, s);
2210 /****************************************************************************
2211 * WCMD_setshow_time
2213 * Set/Show the system time
2214 * FIXME: Can't change time yet
2217 void WCMD_setshow_time (void) {
2219 WCHAR curtime[64], buffer[64];
2220 DWORD count;
2221 SYSTEMTIME st;
2222 static const WCHAR parmT[] = {'/','T','\0'};
2224 if (strlenW(param1) == 0) {
2225 GetLocalTime(&st);
2226 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2227 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2228 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2229 if (strstrW (quals, parmT) == NULL) {
2230 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2231 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2232 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2233 if (count > 2) {
2234 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2238 else WCMD_print_error ();
2240 else {
2241 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2245 /****************************************************************************
2246 * WCMD_shift
2248 * Shift batch parameters.
2249 * Optional /n says where to start shifting (n=0-8)
2252 void WCMD_shift (WCHAR *command) {
2253 int start;
2255 if (context != NULL) {
2256 WCHAR *pos = strchrW(command, '/');
2257 int i;
2259 if (pos == NULL) {
2260 start = 0;
2261 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2262 start = (*(pos+1) - '0');
2263 } else {
2264 SetLastError(ERROR_INVALID_PARAMETER);
2265 WCMD_print_error();
2266 return;
2269 WINE_TRACE("Shifting variables, starting at %d\n", start);
2270 for (i=start;i<=8;i++) {
2271 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2273 context -> shift_count[9] = context -> shift_count[9] + 1;
2278 /****************************************************************************
2279 * WCMD_title
2281 * Set the console title
2283 void WCMD_title (WCHAR *command) {
2284 SetConsoleTitleW(command);
2287 /****************************************************************************
2288 * WCMD_type
2290 * Copy a file to standard output.
2293 void WCMD_type (WCHAR *command) {
2295 int argno = 0;
2296 WCHAR *argN = command;
2297 BOOL writeHeaders = FALSE;
2299 if (param1[0] == 0x00) {
2300 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2301 return;
2304 if (param2[0] != 0x00) writeHeaders = TRUE;
2306 /* Loop through all args */
2307 errorlevel = 0;
2308 while (argN) {
2309 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2311 HANDLE h;
2312 WCHAR buffer[512];
2313 DWORD count;
2315 if (!argN) break;
2317 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2318 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2319 FILE_ATTRIBUTE_NORMAL, NULL);
2320 if (h == INVALID_HANDLE_VALUE) {
2321 WCMD_print_error ();
2322 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2323 errorlevel = 1;
2324 } else {
2325 if (writeHeaders) {
2326 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2327 WCMD_output(fmt, thisArg);
2329 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2330 if (count == 0) break; /* ReadFile reports success on EOF! */
2331 buffer[count] = 0;
2332 WCMD_output_asis (buffer);
2334 CloseHandle (h);
2335 if (!writeHeaders)
2336 WCMD_output_asis (newline);
2341 /****************************************************************************
2342 * WCMD_more
2344 * Output either a file or stdin to screen in pages
2347 void WCMD_more (WCHAR *command) {
2349 int argno = 0;
2350 WCHAR *argN = command;
2351 WCHAR moreStr[100];
2352 WCHAR moreStrPage[100];
2353 WCHAR buffer[512];
2354 DWORD count;
2355 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2356 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2357 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2358 ')',' ','-','-','\n','\0'};
2359 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2361 /* Prefix the NLS more with '-- ', then load the text */
2362 errorlevel = 0;
2363 strcpyW(moreStr, moreStart);
2364 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2365 (sizeof(moreStr)/sizeof(WCHAR))-3);
2367 if (param1[0] == 0x00) {
2369 /* Wine implements pipes via temporary files, and hence stdin is
2370 effectively reading from the file. This means the prompts for
2371 more are satisfied by the next line from the input (file). To
2372 avoid this, ensure stdin is to the console */
2373 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2374 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2375 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2376 FILE_ATTRIBUTE_NORMAL, 0);
2377 WINE_TRACE("No parms - working probably in pipe mode\n");
2378 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2380 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2381 once you get in this bit unless due to a pipe, its going to end badly... */
2382 wsprintfW(moreStrPage, moreFmt, moreStr);
2384 WCMD_enter_paged_mode(moreStrPage);
2385 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2386 if (count == 0) break; /* ReadFile reports success on EOF! */
2387 buffer[count] = 0;
2388 WCMD_output_asis (buffer);
2390 WCMD_leave_paged_mode();
2392 /* Restore stdin to what it was */
2393 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2394 CloseHandle(hConIn);
2396 return;
2397 } else {
2398 BOOL needsPause = FALSE;
2400 /* Loop through all args */
2401 WINE_TRACE("Parms supplied - working through each file\n");
2402 WCMD_enter_paged_mode(moreStrPage);
2404 while (argN) {
2405 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2406 HANDLE h;
2408 if (!argN) break;
2410 if (needsPause) {
2412 /* Wait */
2413 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2414 WCMD_leave_paged_mode();
2415 WCMD_output_asis(moreStrPage);
2416 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2417 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2418 WCMD_enter_paged_mode(moreStrPage);
2422 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2423 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2424 FILE_ATTRIBUTE_NORMAL, NULL);
2425 if (h == INVALID_HANDLE_VALUE) {
2426 WCMD_print_error ();
2427 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2428 errorlevel = 1;
2429 } else {
2430 ULONG64 curPos = 0;
2431 ULONG64 fileLen = 0;
2432 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2434 /* Get the file size */
2435 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2436 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2438 needsPause = TRUE;
2439 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2440 if (count == 0) break; /* ReadFile reports success on EOF! */
2441 buffer[count] = 0;
2442 curPos += count;
2444 /* Update % count (would be used in WCMD_output_asis as prompt) */
2445 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2447 WCMD_output_asis (buffer);
2449 CloseHandle (h);
2453 WCMD_leave_paged_mode();
2457 /****************************************************************************
2458 * WCMD_verify
2460 * Display verify flag.
2461 * FIXME: We don't actually do anything with the verify flag other than toggle
2462 * it...
2465 void WCMD_verify (WCHAR *command) {
2467 int count;
2469 count = strlenW(command);
2470 if (count == 0) {
2471 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2472 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2473 return;
2475 if (lstrcmpiW(command, onW) == 0) {
2476 verify_mode = 1;
2477 return;
2479 else if (lstrcmpiW(command, offW) == 0) {
2480 verify_mode = 0;
2481 return;
2483 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2486 /****************************************************************************
2487 * WCMD_version
2489 * Display version info.
2492 void WCMD_version (void) {
2494 WCMD_output (version_string);
2498 /****************************************************************************
2499 * WCMD_volume
2501 * Display volume info and/or set volume label. Returns 0 if error.
2504 int WCMD_volume (int mode, WCHAR *path) {
2506 DWORD count, serial;
2507 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2508 BOOL status;
2510 if (strlenW(path) == 0) {
2511 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2512 if (!status) {
2513 WCMD_print_error ();
2514 return 0;
2516 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2517 &serial, NULL, NULL, NULL, 0);
2519 else {
2520 static const WCHAR fmt[] = {'%','s','\\','\0'};
2521 if ((path[1] != ':') || (strlenW(path) != 2)) {
2522 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2523 return 0;
2525 wsprintfW (curdir, fmt, path);
2526 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2527 &serial, NULL,
2528 NULL, NULL, 0);
2530 if (!status) {
2531 WCMD_print_error ();
2532 return 0;
2534 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2535 curdir[0], label, HIWORD(serial), LOWORD(serial));
2536 if (mode) {
2537 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2538 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2539 sizeof(string)/sizeof(WCHAR), &count, NULL);
2540 if (count > 1) {
2541 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2542 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2544 if (strlenW(path) != 0) {
2545 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2547 else {
2548 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2551 return 1;
2554 /**************************************************************************
2555 * WCMD_exit
2557 * Exit either the process, or just this batch program
2561 void WCMD_exit (CMD_LIST **cmdList) {
2563 static const WCHAR parmB[] = {'/','B','\0'};
2564 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2566 if (context && lstrcmpiW(quals, parmB) == 0) {
2567 errorlevel = rc;
2568 context -> skip_rest = TRUE;
2569 *cmdList = NULL;
2570 } else {
2571 ExitProcess(rc);
2576 /*****************************************************************************
2577 * WCMD_assoc
2579 * Lists or sets file associations (assoc = TRUE)
2580 * Lists or sets file types (assoc = FALSE)
2582 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2584 HKEY key;
2585 DWORD accessOptions = KEY_READ;
2586 WCHAR *newValue;
2587 LONG rc = ERROR_SUCCESS;
2588 WCHAR keyValue[MAXSTRING];
2589 DWORD valueLen = MAXSTRING;
2590 HKEY readKey;
2591 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2592 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2594 /* See if parameter includes '=' */
2595 errorlevel = 0;
2596 newValue = strchrW(command, '=');
2597 if (newValue) accessOptions |= KEY_WRITE;
2599 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2600 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2601 accessOptions, &key) != ERROR_SUCCESS) {
2602 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2603 return;
2606 /* If no parameters then list all associations */
2607 if (*command == 0x00) {
2608 int index = 0;
2610 /* Enumerate all the keys */
2611 while (rc != ERROR_NO_MORE_ITEMS) {
2612 WCHAR keyName[MAXSTRING];
2613 DWORD nameLen;
2615 /* Find the next value */
2616 nameLen = MAXSTRING;
2617 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2619 if (rc == ERROR_SUCCESS) {
2621 /* Only interested in extension ones if assoc, or others
2622 if not assoc */
2623 if ((keyName[0] == '.' && assoc) ||
2624 (!(keyName[0] == '.') && (!assoc)))
2626 WCHAR subkey[MAXSTRING];
2627 strcpyW(subkey, keyName);
2628 if (!assoc) strcatW(subkey, shOpCmdW);
2630 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2632 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2633 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2634 WCMD_output_asis(keyName);
2635 WCMD_output_asis(equalW);
2636 /* If no default value found, leave line empty after '=' */
2637 if (rc == ERROR_SUCCESS) {
2638 WCMD_output_asis(keyValue);
2640 WCMD_output_asis(newline);
2641 RegCloseKey(readKey);
2647 } else {
2649 /* Parameter supplied - if no '=' on command line, its a query */
2650 if (newValue == NULL) {
2651 WCHAR *space;
2652 WCHAR subkey[MAXSTRING];
2654 /* Query terminates the parameter at the first space */
2655 strcpyW(keyValue, command);
2656 space = strchrW(keyValue, ' ');
2657 if (space) *space=0x00;
2659 /* Set up key name */
2660 strcpyW(subkey, keyValue);
2661 if (!assoc) strcatW(subkey, shOpCmdW);
2663 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2665 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2666 WCMD_output_asis(command);
2667 WCMD_output_asis(equalW);
2668 /* If no default value found, leave line empty after '=' */
2669 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2670 WCMD_output_asis(newline);
2671 RegCloseKey(readKey);
2673 } else {
2674 WCHAR msgbuffer[MAXSTRING];
2675 WCHAR outbuffer[MAXSTRING];
2677 /* Load the translated 'File association not found' */
2678 if (assoc) {
2679 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2680 } else {
2681 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2683 wsprintfW(outbuffer, msgbuffer, keyValue);
2684 WCMD_output_asis(outbuffer);
2685 errorlevel = 2;
2688 /* Not a query - its a set or clear of a value */
2689 } else {
2691 WCHAR subkey[MAXSTRING];
2693 /* Get pointer to new value */
2694 *newValue = 0x00;
2695 newValue++;
2697 /* Set up key name */
2698 strcpyW(subkey, command);
2699 if (!assoc) strcatW(subkey, shOpCmdW);
2701 /* If nothing after '=' then clear value - only valid for ASSOC */
2702 if (*newValue == 0x00) {
2704 if (assoc) rc = RegDeleteKeyW(key, command);
2705 if (assoc && rc == ERROR_SUCCESS) {
2706 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2708 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2709 WCMD_print_error();
2710 errorlevel = 2;
2712 } else {
2713 WCHAR msgbuffer[MAXSTRING];
2714 WCHAR outbuffer[MAXSTRING];
2716 /* Load the translated 'File association not found' */
2717 if (assoc) {
2718 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2719 sizeof(msgbuffer)/sizeof(WCHAR));
2720 } else {
2721 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2722 sizeof(msgbuffer)/sizeof(WCHAR));
2724 wsprintfW(outbuffer, msgbuffer, keyValue);
2725 WCMD_output_asis(outbuffer);
2726 errorlevel = 2;
2729 /* It really is a set value = contents */
2730 } else {
2731 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2732 accessOptions, NULL, &readKey, NULL);
2733 if (rc == ERROR_SUCCESS) {
2734 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2735 (LPBYTE)newValue, strlenW(newValue));
2736 RegCloseKey(readKey);
2739 if (rc != ERROR_SUCCESS) {
2740 WCMD_print_error();
2741 errorlevel = 2;
2742 } else {
2743 WCMD_output_asis(command);
2744 WCMD_output_asis(equalW);
2745 WCMD_output_asis(newValue);
2746 WCMD_output_asis(newline);
2752 /* Clean up */
2753 RegCloseKey(key);
2756 /****************************************************************************
2757 * WCMD_color
2759 * Clear the terminal screen.
2762 void WCMD_color (void) {
2764 /* Emulate by filling the screen from the top left to bottom right with
2765 spaces, then moving the cursor to the top left afterwards */
2766 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2767 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2769 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2770 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2771 return;
2774 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2776 COORD topLeft;
2777 DWORD screenSize;
2778 DWORD color = 0;
2780 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2782 topLeft.X = 0;
2783 topLeft.Y = 0;
2785 /* Convert the color hex digits */
2786 if (param1[0] == 0x00) {
2787 color = defaultColor;
2788 } else {
2789 color = strtoulW(param1, NULL, 16);
2792 /* Fail if fg == bg color */
2793 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2794 errorlevel = 1;
2795 return;
2798 /* Set the current screen contents and ensure all future writes
2799 remain this color */
2800 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2801 SetConsoleTextAttribute(hStdOut, color);