cmdlgtst: Update Korean resource.
[wine/testsucceed.git] / programs / cmd / builtins.c
blob8c3f727f9646d1890daf7a3cc73b56d96d5ca4c2
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];
1153 WCHAR current[MAX_PATH];
1155 /* Do not process any more parts of a processed multipart or multilines command */
1156 if (cmdList) *cmdList = NULL;
1158 if (param1[0] == 0x00) {
1159 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1160 return;
1162 if (context != NULL) {
1163 WCHAR *paramStart = param1, *str;
1164 static const WCHAR eofW[] = {':','e','o','f','\0'};
1166 /* Handle special :EOF label */
1167 if (lstrcmpiW (eofW, param1) == 0) {
1168 context -> skip_rest = TRUE;
1169 return;
1172 /* Support goto :label as well as goto label */
1173 if (*paramStart == ':') paramStart++;
1175 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1176 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1177 str = string;
1178 while (isspaceW (*str)) str++;
1179 if (*str == ':') {
1180 DWORD index = 0;
1181 str++;
1182 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1183 index++;
1185 /* ignore space at the end */
1186 current[index] = 0;
1187 if (lstrcmpiW (current, paramStart) == 0) return;
1190 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1192 return;
1195 /*****************************************************************************
1196 * WCMD_pushd
1198 * Push a directory onto the stack
1201 void WCMD_pushd (WCHAR *command) {
1202 struct env_stack *curdir;
1203 WCHAR *thisdir;
1204 static const WCHAR parmD[] = {'/','D','\0'};
1206 if (strchrW(command, '/') != NULL) {
1207 SetLastError(ERROR_INVALID_PARAMETER);
1208 WCMD_print_error();
1209 return;
1212 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1213 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1214 if( !curdir || !thisdir ) {
1215 LocalFree(curdir);
1216 LocalFree(thisdir);
1217 WINE_ERR ("out of memory\n");
1218 return;
1221 /* Change directory using CD code with /D parameter */
1222 strcpyW(quals, parmD);
1223 GetCurrentDirectoryW (1024, thisdir);
1224 errorlevel = 0;
1225 WCMD_setshow_default(command);
1226 if (errorlevel) {
1227 LocalFree(curdir);
1228 LocalFree(thisdir);
1229 return;
1230 } else {
1231 curdir -> next = pushd_directories;
1232 curdir -> strings = thisdir;
1233 if (pushd_directories == NULL) {
1234 curdir -> u.stackdepth = 1;
1235 } else {
1236 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1238 pushd_directories = curdir;
1243 /*****************************************************************************
1244 * WCMD_popd
1246 * Pop a directory from the stack
1249 void WCMD_popd (void) {
1250 struct env_stack *temp = pushd_directories;
1252 if (!pushd_directories)
1253 return;
1255 /* pop the old environment from the stack, and make it the current dir */
1256 pushd_directories = temp->next;
1257 SetCurrentDirectoryW(temp->strings);
1258 LocalFree (temp->strings);
1259 LocalFree (temp);
1262 /****************************************************************************
1263 * WCMD_if
1265 * Batch file conditional.
1267 * On entry, cmdlist will point to command containing the IF, and optionally
1268 * the first command to execute (if brackets not found)
1269 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1270 * If ('s were found, execute all within that bracket
1271 * Command may optionally be followed by an ELSE - need to skip instructions
1272 * in the else using the same logic
1274 * FIXME: Much more syntax checking needed!
1277 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1279 int negate = 0, test = 0;
1280 WCHAR condition[MAX_PATH], *command, *s;
1281 static const WCHAR notW[] = {'n','o','t','\0'};
1282 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1283 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1284 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1285 static const WCHAR eqeqW[] = {'=','=','\0'};
1286 static const WCHAR parmI[] = {'/','I','\0'};
1288 if (!lstrcmpiW (param1, notW)) {
1289 negate = 1;
1290 strcpyW (condition, param2);
1292 else {
1293 strcpyW (condition, param1);
1295 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1297 if (!lstrcmpiW (condition, errlvlW)) {
1298 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1299 WCMD_parameter (p, 2+negate, &command);
1301 else if (!lstrcmpiW (condition, existW)) {
1302 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1303 test = 1;
1305 WCMD_parameter (p, 2+negate, &command);
1307 else if (!lstrcmpiW (condition, defdW)) {
1308 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1309 test = 1;
1311 WCMD_parameter (p, 2+negate, &command);
1313 else if ((s = strstrW (p, eqeqW))) {
1314 s += 2;
1315 if (strstrW (quals, parmI) == NULL) {
1316 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1318 else {
1319 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1321 WCMD_parameter (s, 1, &command);
1323 else {
1324 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1325 return;
1328 /* Process rest of IF statement which is on the same line
1329 Note: This may process all or some of the cmdList (eg a GOTO) */
1330 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1333 /****************************************************************************
1334 * WCMD_move
1336 * Move a file, directory tree or wildcarded set of files.
1339 void WCMD_move (void) {
1341 int status;
1342 WIN32_FIND_DATAW fd;
1343 HANDLE hff;
1344 WCHAR input[MAX_PATH];
1345 WCHAR output[MAX_PATH];
1346 WCHAR drive[10];
1347 WCHAR dir[MAX_PATH];
1348 WCHAR fname[MAX_PATH];
1349 WCHAR ext[MAX_PATH];
1351 if (param1[0] == 0x00) {
1352 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1353 return;
1356 /* If no destination supplied, assume current directory */
1357 if (param2[0] == 0x00) {
1358 strcpyW(param2, dotW);
1361 /* If 2nd parm is directory, then use original filename */
1362 /* Convert partial path to full path */
1363 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1364 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1365 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1366 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1368 /* Split into components */
1369 WCMD_splitpath(input, drive, dir, fname, ext);
1371 hff = FindFirstFileW(input, &fd);
1372 while (hff != INVALID_HANDLE_VALUE) {
1373 WCHAR dest[MAX_PATH];
1374 WCHAR src[MAX_PATH];
1375 DWORD attribs;
1377 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1379 /* Build src & dest name */
1380 strcpyW(src, drive);
1381 strcatW(src, dir);
1383 /* See if dest is an existing directory */
1384 attribs = GetFileAttributesW(output);
1385 if (attribs != INVALID_FILE_ATTRIBUTES &&
1386 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1387 strcpyW(dest, output);
1388 strcatW(dest, slashW);
1389 strcatW(dest, fd.cFileName);
1390 } else {
1391 strcpyW(dest, output);
1394 strcatW(src, fd.cFileName);
1396 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1397 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1399 /* Check if file is read only, otherwise move it */
1400 attribs = GetFileAttributesW(src);
1401 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1402 (attribs & FILE_ATTRIBUTE_READONLY)) {
1403 SetLastError(ERROR_ACCESS_DENIED);
1404 status = 0;
1405 } else {
1406 BOOL ok = TRUE;
1408 /* If destination exists, prompt unless /Y supplied */
1409 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1410 BOOL force = FALSE;
1411 WCHAR copycmd[MAXSTRING];
1412 int len;
1414 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1415 if (strstrW (quals, parmNoY))
1416 force = FALSE;
1417 else if (strstrW (quals, parmY))
1418 force = TRUE;
1419 else {
1420 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1421 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1422 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1423 && ! lstrcmpiW (copycmd, parmY));
1426 /* Prompt if overwriting */
1427 if (!force) {
1428 WCHAR question[MAXSTRING];
1429 WCHAR yesChar[10];
1431 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1433 /* Ask for confirmation */
1434 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1435 ok = WCMD_ask_confirm(question, FALSE, NULL);
1437 /* So delete the destination prior to the move */
1438 if (ok) {
1439 if (!DeleteFileW(dest)) {
1440 WCMD_print_error ();
1441 errorlevel = 1;
1442 ok = FALSE;
1448 if (ok) {
1449 status = MoveFileW(src, dest);
1450 } else {
1451 status = 1; /* Anything other than 0 to prevent error msg below */
1455 if (!status) {
1456 WCMD_print_error ();
1457 errorlevel = 1;
1460 /* Step on to next match */
1461 if (FindNextFileW(hff, &fd) == 0) {
1462 FindClose(hff);
1463 hff = INVALID_HANDLE_VALUE;
1464 break;
1469 /****************************************************************************
1470 * WCMD_pause
1472 * Wait for keyboard input.
1475 void WCMD_pause (void) {
1477 DWORD count;
1478 WCHAR string[32];
1480 WCMD_output (anykey);
1481 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1482 sizeof(string)/sizeof(WCHAR), &count, NULL);
1485 /****************************************************************************
1486 * WCMD_remove_dir
1488 * Delete a directory.
1491 void WCMD_remove_dir (WCHAR *command) {
1493 int argno = 0;
1494 int argsProcessed = 0;
1495 WCHAR *argN = command;
1496 static const WCHAR parmS[] = {'/','S','\0'};
1497 static const WCHAR parmQ[] = {'/','Q','\0'};
1499 /* Loop through all args */
1500 while (argN) {
1501 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1502 if (argN && argN[0] != '/') {
1503 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1504 wine_dbgstr_w(quals));
1505 argsProcessed++;
1507 /* If subdirectory search not supplied, just try to remove
1508 and report error if it fails (eg if it contains a file) */
1509 if (strstrW (quals, parmS) == NULL) {
1510 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1512 /* Otherwise use ShFileOp to recursively remove a directory */
1513 } else {
1515 SHFILEOPSTRUCTW lpDir;
1517 /* Ask first */
1518 if (strstrW (quals, parmQ) == NULL) {
1519 BOOL ok;
1520 WCHAR question[MAXSTRING];
1521 static const WCHAR fmt[] = {'%','s',' ','\0'};
1523 /* Ask for confirmation */
1524 wsprintfW(question, fmt, thisArg);
1525 ok = WCMD_ask_confirm(question, TRUE, NULL);
1527 /* Abort if answer is 'N' */
1528 if (!ok) return;
1531 /* Do the delete */
1532 lpDir.hwnd = NULL;
1533 lpDir.pTo = NULL;
1534 lpDir.pFrom = thisArg;
1535 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1536 lpDir.wFunc = FO_DELETE;
1537 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1542 /* Handle no valid args */
1543 if (argsProcessed == 0) {
1544 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1545 return;
1550 /****************************************************************************
1551 * WCMD_rename
1553 * Rename a file.
1556 void WCMD_rename (void) {
1558 int status;
1559 HANDLE hff;
1560 WIN32_FIND_DATAW fd;
1561 WCHAR input[MAX_PATH];
1562 WCHAR *dotDst = NULL;
1563 WCHAR drive[10];
1564 WCHAR dir[MAX_PATH];
1565 WCHAR fname[MAX_PATH];
1566 WCHAR ext[MAX_PATH];
1567 DWORD attribs;
1569 errorlevel = 0;
1571 /* Must be at least two args */
1572 if (param1[0] == 0x00 || param2[0] == 0x00) {
1573 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1574 errorlevel = 1;
1575 return;
1578 /* Destination cannot contain a drive letter or directory separator */
1579 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1580 SetLastError(ERROR_INVALID_PARAMETER);
1581 WCMD_print_error();
1582 errorlevel = 1;
1583 return;
1586 /* Convert partial path to full path */
1587 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1588 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1589 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1590 dotDst = strchrW(param2, '.');
1592 /* Split into components */
1593 WCMD_splitpath(input, drive, dir, fname, ext);
1595 hff = FindFirstFileW(input, &fd);
1596 while (hff != INVALID_HANDLE_VALUE) {
1597 WCHAR dest[MAX_PATH];
1598 WCHAR src[MAX_PATH];
1599 WCHAR *dotSrc = NULL;
1600 int dirLen;
1602 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1604 /* FIXME: If dest name or extension is *, replace with filename/ext
1605 part otherwise use supplied name. This supports:
1606 ren *.fred *.jim
1607 ren jim.* fred.* etc
1608 However, windows has a more complex algorithm supporting eg
1609 ?'s and *'s mid name */
1610 dotSrc = strchrW(fd.cFileName, '.');
1612 /* Build src & dest name */
1613 strcpyW(src, drive);
1614 strcatW(src, dir);
1615 strcpyW(dest, src);
1616 dirLen = strlenW(src);
1617 strcatW(src, fd.cFileName);
1619 /* Build name */
1620 if (param2[0] == '*') {
1621 strcatW(dest, fd.cFileName);
1622 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1623 } else {
1624 strcatW(dest, param2);
1625 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1628 /* Build Extension */
1629 if (dotDst && (*(dotDst+1)=='*')) {
1630 if (dotSrc) strcatW(dest, dotSrc);
1631 } else if (dotDst) {
1632 if (dotDst) strcatW(dest, dotDst);
1635 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1636 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1638 /* Check if file is read only, otherwise move it */
1639 attribs = GetFileAttributesW(src);
1640 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1641 (attribs & FILE_ATTRIBUTE_READONLY)) {
1642 SetLastError(ERROR_ACCESS_DENIED);
1643 status = 0;
1644 } else {
1645 status = MoveFileW(src, dest);
1648 if (!status) {
1649 WCMD_print_error ();
1650 errorlevel = 1;
1653 /* Step on to next match */
1654 if (FindNextFileW(hff, &fd) == 0) {
1655 FindClose(hff);
1656 hff = INVALID_HANDLE_VALUE;
1657 break;
1662 /*****************************************************************************
1663 * WCMD_dupenv
1665 * Make a copy of the environment.
1667 static WCHAR *WCMD_dupenv( const WCHAR *env )
1669 WCHAR *env_copy;
1670 int len;
1672 if( !env )
1673 return NULL;
1675 len = 0;
1676 while ( env[len] )
1677 len += (strlenW(&env[len]) + 1);
1679 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1680 if (!env_copy)
1682 WINE_ERR("out of memory\n");
1683 return env_copy;
1685 memcpy (env_copy, env, len*sizeof (WCHAR));
1686 env_copy[len] = 0;
1688 return env_copy;
1691 /*****************************************************************************
1692 * WCMD_setlocal
1694 * setlocal pushes the environment onto a stack
1695 * Save the environment as unicode so we don't screw anything up.
1697 void WCMD_setlocal (const WCHAR *s) {
1698 WCHAR *env;
1699 struct env_stack *env_copy;
1700 WCHAR cwd[MAX_PATH];
1702 /* DISABLEEXTENSIONS ignored */
1704 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1705 if( !env_copy )
1707 WINE_ERR ("out of memory\n");
1708 return;
1711 env = GetEnvironmentStringsW ();
1713 env_copy->strings = WCMD_dupenv (env);
1714 if (env_copy->strings)
1716 env_copy->next = saved_environment;
1717 saved_environment = env_copy;
1719 /* Save the current drive letter */
1720 GetCurrentDirectoryW(MAX_PATH, cwd);
1721 env_copy->u.cwd = cwd[0];
1723 else
1724 LocalFree (env_copy);
1726 FreeEnvironmentStringsW (env);
1730 /*****************************************************************************
1731 * WCMD_endlocal
1733 * endlocal pops the environment off a stack
1734 * Note: When searching for '=', search from WCHAR position 1, to handle
1735 * special internal environment variables =C:, =D: etc
1737 void WCMD_endlocal (void) {
1738 WCHAR *env, *old, *p;
1739 struct env_stack *temp;
1740 int len, n;
1742 if (!saved_environment)
1743 return;
1745 /* pop the old environment from the stack */
1746 temp = saved_environment;
1747 saved_environment = temp->next;
1749 /* delete the current environment, totally */
1750 env = GetEnvironmentStringsW ();
1751 old = WCMD_dupenv (GetEnvironmentStringsW ());
1752 len = 0;
1753 while (old[len]) {
1754 n = strlenW(&old[len]) + 1;
1755 p = strchrW(&old[len] + 1, '=');
1756 if (p)
1758 *p++ = 0;
1759 SetEnvironmentVariableW (&old[len], NULL);
1761 len += n;
1763 LocalFree (old);
1764 FreeEnvironmentStringsW (env);
1766 /* restore old environment */
1767 env = temp->strings;
1768 len = 0;
1769 while (env[len]) {
1770 n = strlenW(&env[len]) + 1;
1771 p = strchrW(&env[len] + 1, '=');
1772 if (p)
1774 *p++ = 0;
1775 SetEnvironmentVariableW (&env[len], p);
1777 len += n;
1780 /* Restore current drive letter */
1781 if (IsCharAlphaW(temp->u.cwd)) {
1782 WCHAR envvar[4];
1783 WCHAR cwd[MAX_PATH];
1784 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1786 wsprintfW(envvar, fmt, temp->u.cwd);
1787 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1788 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1789 SetCurrentDirectoryW(cwd);
1793 LocalFree (env);
1794 LocalFree (temp);
1797 /*****************************************************************************
1798 * WCMD_setshow_attrib
1800 * Display and optionally sets DOS attributes on a file or directory
1804 void WCMD_setshow_attrib (void) {
1806 DWORD count;
1807 HANDLE hff;
1808 WIN32_FIND_DATAW fd;
1809 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1810 WCHAR *name = param1;
1811 DWORD attrib_set=0;
1812 DWORD attrib_clear=0;
1814 if (param1[0] == '+' || param1[0] == '-') {
1815 DWORD attrib = 0;
1816 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
1817 switch (param1[1]) {
1818 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
1819 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
1820 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
1821 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
1822 default:
1823 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1824 return;
1826 switch (param1[0]) {
1827 case '+': attrib_set = attrib; break;
1828 case '-': attrib_clear = attrib; break;
1830 name = param2;
1833 if (strlenW(name) == 0) {
1834 static const WCHAR slashStarW[] = {'\\','*','\0'};
1836 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
1837 strcatW (name, slashStarW);
1840 hff = FindFirstFileW(name, &fd);
1841 if (hff == INVALID_HANDLE_VALUE) {
1842 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
1844 else {
1845 do {
1846 if (attrib_set || attrib_clear) {
1847 fd.dwFileAttributes &= ~attrib_clear;
1848 fd.dwFileAttributes |= attrib_set;
1849 if (!fd.dwFileAttributes)
1850 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
1851 SetFileAttributesW(name, fd.dwFileAttributes);
1852 } else {
1853 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1854 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1855 flags[0] = 'H';
1857 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1858 flags[1] = 'S';
1860 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1861 flags[2] = 'A';
1863 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1864 flags[3] = 'R';
1866 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1867 flags[4] = 'T';
1869 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1870 flags[5] = 'C';
1872 WCMD_output (fmt, flags, fd.cFileName);
1873 for (count=0; count < 8; count++) flags[count] = ' ';
1875 } while (FindNextFileW(hff, &fd) != 0);
1877 FindClose (hff);
1880 /*****************************************************************************
1881 * WCMD_setshow_default
1883 * Set/Show the current default directory
1886 void WCMD_setshow_default (WCHAR *command) {
1888 BOOL status;
1889 WCHAR string[1024];
1890 WCHAR cwd[1024];
1891 WCHAR *pos;
1892 WIN32_FIND_DATAW fd;
1893 HANDLE hff;
1894 static const WCHAR parmD[] = {'/','D','\0'};
1896 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1898 /* Skip /D and trailing whitespace if on the front of the command line */
1899 if (CompareStringW(LOCALE_USER_DEFAULT,
1900 NORM_IGNORECASE | SORT_STRINGSORT,
1901 command, 2, parmD, -1) == 2) {
1902 command += 2;
1903 while (*command && *command==' ') command++;
1906 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
1907 if (strlenW(command) == 0) {
1908 strcatW (cwd, newline);
1909 WCMD_output (cwd);
1911 else {
1912 /* Remove any double quotes, which may be in the
1913 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1914 pos = string;
1915 while (*command) {
1916 if (*command != '"') *pos++ = *command;
1917 command++;
1919 *pos = 0x00;
1921 /* Search for appropriate directory */
1922 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1923 hff = FindFirstFileW(string, &fd);
1924 while (hff != INVALID_HANDLE_VALUE) {
1925 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1926 WCHAR fpath[MAX_PATH];
1927 WCHAR drive[10];
1928 WCHAR dir[MAX_PATH];
1929 WCHAR fname[MAX_PATH];
1930 WCHAR ext[MAX_PATH];
1931 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1933 /* Convert path into actual directory spec */
1934 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1935 WCMD_splitpath(fpath, drive, dir, fname, ext);
1937 /* Rebuild path */
1938 wsprintfW(string, fmt, drive, dir, fd.cFileName);
1940 FindClose(hff);
1941 hff = INVALID_HANDLE_VALUE;
1942 break;
1945 /* Step on to next match */
1946 if (FindNextFileW(hff, &fd) == 0) {
1947 FindClose(hff);
1948 hff = INVALID_HANDLE_VALUE;
1949 break;
1953 /* Change to that directory */
1954 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1956 status = SetCurrentDirectoryW(string);
1957 if (!status) {
1958 errorlevel = 1;
1959 WCMD_print_error ();
1960 return;
1961 } else {
1963 /* Save away the actual new directory, to store as current location */
1964 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
1966 /* Restore old directory if drive letter would change, and
1967 CD x:\directory /D (or pushd c:\directory) not supplied */
1968 if ((strstrW(quals, parmD) == NULL) &&
1969 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1970 SetCurrentDirectoryW(cwd);
1974 /* Set special =C: type environment variable, for drive letter of
1975 change of directory, even if path was restored due to missing
1976 /D (allows changing drive letter when not resident on that
1977 drive */
1978 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
1979 WCHAR env[4];
1980 strcpyW(env, equalW);
1981 memcpy(env+1, string, 2 * sizeof(WCHAR));
1982 env[3] = 0x00;
1983 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1984 SetEnvironmentVariableW(env, string);
1988 return;
1991 /****************************************************************************
1992 * WCMD_setshow_date
1994 * Set/Show the system date
1995 * FIXME: Can't change date yet
1998 void WCMD_setshow_date (void) {
2000 WCHAR curdate[64], buffer[64];
2001 DWORD count;
2002 static const WCHAR parmT[] = {'/','T','\0'};
2004 if (strlenW(param1) == 0) {
2005 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2006 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2007 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2008 if (strstrW (quals, parmT) == NULL) {
2009 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2010 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2011 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2012 if (count > 2) {
2013 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2017 else WCMD_print_error ();
2019 else {
2020 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2024 /****************************************************************************
2025 * WCMD_compare
2027 static int WCMD_compare( const void *a, const void *b )
2029 int r;
2030 const WCHAR * const *str_a = a, * const *str_b = b;
2031 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2032 *str_a, -1, *str_b, -1 );
2033 if( r == CSTR_LESS_THAN ) return -1;
2034 if( r == CSTR_GREATER_THAN ) return 1;
2035 return 0;
2038 /****************************************************************************
2039 * WCMD_setshow_sortenv
2041 * sort variables into order for display
2042 * Optionally only display those who start with a stub
2043 * returns the count displayed
2045 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2047 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2048 const WCHAR **str;
2050 if (stub) stublen = strlenW(stub);
2052 /* count the number of strings, and the total length */
2053 while ( s[len] ) {
2054 len += (strlenW(&s[len]) + 1);
2055 count++;
2058 /* add the strings to an array */
2059 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2060 if( !str )
2061 return 0;
2062 str[0] = s;
2063 for( i=1; i<count; i++ )
2064 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2066 /* sort the array */
2067 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2069 /* print it */
2070 for( i=0; i<count; i++ ) {
2071 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2072 NORM_IGNORECASE | SORT_STRINGSORT,
2073 str[i], stublen, stub, -1) == 2) {
2074 /* Don't display special internal variables */
2075 if (str[i][0] != '=') {
2076 WCMD_output_asis(str[i]);
2077 WCMD_output_asis(newline);
2078 displayedcount++;
2083 LocalFree( str );
2084 return displayedcount;
2087 /****************************************************************************
2088 * WCMD_setshow_env
2090 * Set/Show the environment variables
2093 void WCMD_setshow_env (WCHAR *s) {
2095 LPVOID env;
2096 WCHAR *p;
2097 int status;
2098 static const WCHAR parmP[] = {'/','P','\0'};
2100 errorlevel = 0;
2101 if (param1[0] == 0x00 && quals[0] == 0x00) {
2102 env = GetEnvironmentStringsW();
2103 WCMD_setshow_sortenv( env, NULL );
2104 return;
2107 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2108 if (CompareStringW(LOCALE_USER_DEFAULT,
2109 NORM_IGNORECASE | SORT_STRINGSORT,
2110 s, 2, parmP, -1) == 2) {
2111 WCHAR string[MAXSTRING];
2112 DWORD count;
2114 s += 2;
2115 while (*s && *s==' ') s++;
2116 if (*s=='\"')
2117 WCMD_opt_s_strip_quotes(s);
2119 /* If no parameter, or no '=' sign, return an error */
2120 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2121 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2122 return;
2125 /* Output the prompt */
2126 *p++ = '\0';
2127 if (strlenW(p) != 0) WCMD_output(p);
2129 /* Read the reply */
2130 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2131 sizeof(string)/sizeof(WCHAR), &count, NULL);
2132 if (count > 1) {
2133 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2134 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2135 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2136 wine_dbgstr_w(string));
2137 status = SetEnvironmentVariableW(s, string);
2140 } else {
2141 DWORD gle;
2143 if (*s=='\"')
2144 WCMD_opt_s_strip_quotes(s);
2145 p = strchrW (s, '=');
2146 if (p == NULL) {
2147 env = GetEnvironmentStringsW();
2148 if (WCMD_setshow_sortenv( env, s ) == 0) {
2149 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2150 errorlevel = 1;
2152 return;
2154 *p++ = '\0';
2156 if (strlenW(p) == 0) p = NULL;
2157 status = SetEnvironmentVariableW(s, p);
2158 gle = GetLastError();
2159 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2160 errorlevel = 1;
2161 } else if ((!status)) WCMD_print_error();
2165 /****************************************************************************
2166 * WCMD_setshow_path
2168 * Set/Show the path environment variable
2171 void WCMD_setshow_path (WCHAR *command) {
2173 WCHAR string[1024];
2174 DWORD status;
2175 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2176 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2178 if (strlenW(param1) == 0) {
2179 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2180 if (status != 0) {
2181 WCMD_output_asis ( pathEqW);
2182 WCMD_output_asis ( string);
2183 WCMD_output_asis ( newline);
2185 else {
2186 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2189 else {
2190 if (*command == '=') command++; /* Skip leading '=' */
2191 status = SetEnvironmentVariableW(pathW, command);
2192 if (!status) WCMD_print_error();
2196 /****************************************************************************
2197 * WCMD_setshow_prompt
2199 * Set or show the command prompt.
2202 void WCMD_setshow_prompt (void) {
2204 WCHAR *s;
2205 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2207 if (strlenW(param1) == 0) {
2208 SetEnvironmentVariableW(promptW, NULL);
2210 else {
2211 s = param1;
2212 while ((*s == '=') || (*s == ' ')) s++;
2213 if (strlenW(s) == 0) {
2214 SetEnvironmentVariableW(promptW, NULL);
2216 else SetEnvironmentVariableW(promptW, s);
2220 /****************************************************************************
2221 * WCMD_setshow_time
2223 * Set/Show the system time
2224 * FIXME: Can't change time yet
2227 void WCMD_setshow_time (void) {
2229 WCHAR curtime[64], buffer[64];
2230 DWORD count;
2231 SYSTEMTIME st;
2232 static const WCHAR parmT[] = {'/','T','\0'};
2234 if (strlenW(param1) == 0) {
2235 GetLocalTime(&st);
2236 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2237 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2238 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2239 if (strstrW (quals, parmT) == NULL) {
2240 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2241 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2242 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2243 if (count > 2) {
2244 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2248 else WCMD_print_error ();
2250 else {
2251 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2255 /****************************************************************************
2256 * WCMD_shift
2258 * Shift batch parameters.
2259 * Optional /n says where to start shifting (n=0-8)
2262 void WCMD_shift (WCHAR *command) {
2263 int start;
2265 if (context != NULL) {
2266 WCHAR *pos = strchrW(command, '/');
2267 int i;
2269 if (pos == NULL) {
2270 start = 0;
2271 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2272 start = (*(pos+1) - '0');
2273 } else {
2274 SetLastError(ERROR_INVALID_PARAMETER);
2275 WCMD_print_error();
2276 return;
2279 WINE_TRACE("Shifting variables, starting at %d\n", start);
2280 for (i=start;i<=8;i++) {
2281 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2283 context -> shift_count[9] = context -> shift_count[9] + 1;
2288 /****************************************************************************
2289 * WCMD_title
2291 * Set the console title
2293 void WCMD_title (WCHAR *command) {
2294 SetConsoleTitleW(command);
2297 /****************************************************************************
2298 * WCMD_type
2300 * Copy a file to standard output.
2303 void WCMD_type (WCHAR *command) {
2305 int argno = 0;
2306 WCHAR *argN = command;
2307 BOOL writeHeaders = FALSE;
2309 if (param1[0] == 0x00) {
2310 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2311 return;
2314 if (param2[0] != 0x00) writeHeaders = TRUE;
2316 /* Loop through all args */
2317 errorlevel = 0;
2318 while (argN) {
2319 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2321 HANDLE h;
2322 WCHAR buffer[512];
2323 DWORD count;
2325 if (!argN) break;
2327 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2328 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2329 FILE_ATTRIBUTE_NORMAL, NULL);
2330 if (h == INVALID_HANDLE_VALUE) {
2331 WCMD_print_error ();
2332 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2333 errorlevel = 1;
2334 } else {
2335 if (writeHeaders) {
2336 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2337 WCMD_output(fmt, thisArg);
2339 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2340 if (count == 0) break; /* ReadFile reports success on EOF! */
2341 buffer[count] = 0;
2342 WCMD_output_asis (buffer);
2344 CloseHandle (h);
2345 if (!writeHeaders)
2346 WCMD_output_asis (newline);
2351 /****************************************************************************
2352 * WCMD_more
2354 * Output either a file or stdin to screen in pages
2357 void WCMD_more (WCHAR *command) {
2359 int argno = 0;
2360 WCHAR *argN = command;
2361 WCHAR moreStr[100];
2362 WCHAR moreStrPage[100];
2363 WCHAR buffer[512];
2364 DWORD count;
2365 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2366 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2367 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2368 ')',' ','-','-','\n','\0'};
2369 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2371 /* Prefix the NLS more with '-- ', then load the text */
2372 errorlevel = 0;
2373 strcpyW(moreStr, moreStart);
2374 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2375 (sizeof(moreStr)/sizeof(WCHAR))-3);
2377 if (param1[0] == 0x00) {
2379 /* Wine implements pipes via temporary files, and hence stdin is
2380 effectively reading from the file. This means the prompts for
2381 more are satisfied by the next line from the input (file). To
2382 avoid this, ensure stdin is to the console */
2383 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2384 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2385 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2386 FILE_ATTRIBUTE_NORMAL, 0);
2387 WINE_TRACE("No parms - working probably in pipe mode\n");
2388 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2390 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2391 once you get in this bit unless due to a pipe, its going to end badly... */
2392 wsprintfW(moreStrPage, moreFmt, moreStr);
2394 WCMD_enter_paged_mode(moreStrPage);
2395 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2396 if (count == 0) break; /* ReadFile reports success on EOF! */
2397 buffer[count] = 0;
2398 WCMD_output_asis (buffer);
2400 WCMD_leave_paged_mode();
2402 /* Restore stdin to what it was */
2403 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2404 CloseHandle(hConIn);
2406 return;
2407 } else {
2408 BOOL needsPause = FALSE;
2410 /* Loop through all args */
2411 WINE_TRACE("Parms supplied - working through each file\n");
2412 WCMD_enter_paged_mode(moreStrPage);
2414 while (argN) {
2415 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2416 HANDLE h;
2418 if (!argN) break;
2420 if (needsPause) {
2422 /* Wait */
2423 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2424 WCMD_leave_paged_mode();
2425 WCMD_output_asis(moreStrPage);
2426 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2427 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2428 WCMD_enter_paged_mode(moreStrPage);
2432 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2433 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2434 FILE_ATTRIBUTE_NORMAL, NULL);
2435 if (h == INVALID_HANDLE_VALUE) {
2436 WCMD_print_error ();
2437 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2438 errorlevel = 1;
2439 } else {
2440 ULONG64 curPos = 0;
2441 ULONG64 fileLen = 0;
2442 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2444 /* Get the file size */
2445 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2446 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2448 needsPause = TRUE;
2449 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2450 if (count == 0) break; /* ReadFile reports success on EOF! */
2451 buffer[count] = 0;
2452 curPos += count;
2454 /* Update % count (would be used in WCMD_output_asis as prompt) */
2455 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2457 WCMD_output_asis (buffer);
2459 CloseHandle (h);
2463 WCMD_leave_paged_mode();
2467 /****************************************************************************
2468 * WCMD_verify
2470 * Display verify flag.
2471 * FIXME: We don't actually do anything with the verify flag other than toggle
2472 * it...
2475 void WCMD_verify (WCHAR *command) {
2477 int count;
2479 count = strlenW(command);
2480 if (count == 0) {
2481 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2482 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2483 return;
2485 if (lstrcmpiW(command, onW) == 0) {
2486 verify_mode = 1;
2487 return;
2489 else if (lstrcmpiW(command, offW) == 0) {
2490 verify_mode = 0;
2491 return;
2493 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2496 /****************************************************************************
2497 * WCMD_version
2499 * Display version info.
2502 void WCMD_version (void) {
2504 WCMD_output (version_string);
2508 /****************************************************************************
2509 * WCMD_volume
2511 * Display volume info and/or set volume label. Returns 0 if error.
2514 int WCMD_volume (int mode, WCHAR *path) {
2516 DWORD count, serial;
2517 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2518 BOOL status;
2520 if (strlenW(path) == 0) {
2521 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2522 if (!status) {
2523 WCMD_print_error ();
2524 return 0;
2526 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2527 &serial, NULL, NULL, NULL, 0);
2529 else {
2530 static const WCHAR fmt[] = {'%','s','\\','\0'};
2531 if ((path[1] != ':') || (strlenW(path) != 2)) {
2532 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2533 return 0;
2535 wsprintfW (curdir, fmt, path);
2536 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2537 &serial, NULL,
2538 NULL, NULL, 0);
2540 if (!status) {
2541 WCMD_print_error ();
2542 return 0;
2544 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2545 curdir[0], label, HIWORD(serial), LOWORD(serial));
2546 if (mode) {
2547 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2548 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2549 sizeof(string)/sizeof(WCHAR), &count, NULL);
2550 if (count > 1) {
2551 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2552 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2554 if (strlenW(path) != 0) {
2555 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2557 else {
2558 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2561 return 1;
2564 /**************************************************************************
2565 * WCMD_exit
2567 * Exit either the process, or just this batch program
2571 void WCMD_exit (CMD_LIST **cmdList) {
2573 static const WCHAR parmB[] = {'/','B','\0'};
2574 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2576 if (context && lstrcmpiW(quals, parmB) == 0) {
2577 errorlevel = rc;
2578 context -> skip_rest = TRUE;
2579 *cmdList = NULL;
2580 } else {
2581 ExitProcess(rc);
2586 /*****************************************************************************
2587 * WCMD_assoc
2589 * Lists or sets file associations (assoc = TRUE)
2590 * Lists or sets file types (assoc = FALSE)
2592 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2594 HKEY key;
2595 DWORD accessOptions = KEY_READ;
2596 WCHAR *newValue;
2597 LONG rc = ERROR_SUCCESS;
2598 WCHAR keyValue[MAXSTRING];
2599 DWORD valueLen = MAXSTRING;
2600 HKEY readKey;
2601 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2602 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2604 /* See if parameter includes '=' */
2605 errorlevel = 0;
2606 newValue = strchrW(command, '=');
2607 if (newValue) accessOptions |= KEY_WRITE;
2609 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2610 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2611 accessOptions, &key) != ERROR_SUCCESS) {
2612 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2613 return;
2616 /* If no parameters then list all associations */
2617 if (*command == 0x00) {
2618 int index = 0;
2620 /* Enumerate all the keys */
2621 while (rc != ERROR_NO_MORE_ITEMS) {
2622 WCHAR keyName[MAXSTRING];
2623 DWORD nameLen;
2625 /* Find the next value */
2626 nameLen = MAXSTRING;
2627 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2629 if (rc == ERROR_SUCCESS) {
2631 /* Only interested in extension ones if assoc, or others
2632 if not assoc */
2633 if ((keyName[0] == '.' && assoc) ||
2634 (!(keyName[0] == '.') && (!assoc)))
2636 WCHAR subkey[MAXSTRING];
2637 strcpyW(subkey, keyName);
2638 if (!assoc) strcatW(subkey, shOpCmdW);
2640 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2642 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2643 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2644 WCMD_output_asis(keyName);
2645 WCMD_output_asis(equalW);
2646 /* If no default value found, leave line empty after '=' */
2647 if (rc == ERROR_SUCCESS) {
2648 WCMD_output_asis(keyValue);
2650 WCMD_output_asis(newline);
2651 RegCloseKey(readKey);
2657 } else {
2659 /* Parameter supplied - if no '=' on command line, its a query */
2660 if (newValue == NULL) {
2661 WCHAR *space;
2662 WCHAR subkey[MAXSTRING];
2664 /* Query terminates the parameter at the first space */
2665 strcpyW(keyValue, command);
2666 space = strchrW(keyValue, ' ');
2667 if (space) *space=0x00;
2669 /* Set up key name */
2670 strcpyW(subkey, keyValue);
2671 if (!assoc) strcatW(subkey, shOpCmdW);
2673 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2675 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2676 WCMD_output_asis(command);
2677 WCMD_output_asis(equalW);
2678 /* If no default value found, leave line empty after '=' */
2679 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2680 WCMD_output_asis(newline);
2681 RegCloseKey(readKey);
2683 } else {
2684 WCHAR msgbuffer[MAXSTRING];
2685 WCHAR outbuffer[MAXSTRING];
2687 /* Load the translated 'File association not found' */
2688 if (assoc) {
2689 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2690 } else {
2691 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2693 wsprintfW(outbuffer, msgbuffer, keyValue);
2694 WCMD_output_asis(outbuffer);
2695 errorlevel = 2;
2698 /* Not a query - its a set or clear of a value */
2699 } else {
2701 WCHAR subkey[MAXSTRING];
2703 /* Get pointer to new value */
2704 *newValue = 0x00;
2705 newValue++;
2707 /* Set up key name */
2708 strcpyW(subkey, command);
2709 if (!assoc) strcatW(subkey, shOpCmdW);
2711 /* If nothing after '=' then clear value - only valid for ASSOC */
2712 if (*newValue == 0x00) {
2714 if (assoc) rc = RegDeleteKeyW(key, command);
2715 if (assoc && rc == ERROR_SUCCESS) {
2716 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2718 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2719 WCMD_print_error();
2720 errorlevel = 2;
2722 } else {
2723 WCHAR msgbuffer[MAXSTRING];
2724 WCHAR outbuffer[MAXSTRING];
2726 /* Load the translated 'File association not found' */
2727 if (assoc) {
2728 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2729 sizeof(msgbuffer)/sizeof(WCHAR));
2730 } else {
2731 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2732 sizeof(msgbuffer)/sizeof(WCHAR));
2734 wsprintfW(outbuffer, msgbuffer, keyValue);
2735 WCMD_output_asis(outbuffer);
2736 errorlevel = 2;
2739 /* It really is a set value = contents */
2740 } else {
2741 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2742 accessOptions, NULL, &readKey, NULL);
2743 if (rc == ERROR_SUCCESS) {
2744 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2745 (LPBYTE)newValue, strlenW(newValue));
2746 RegCloseKey(readKey);
2749 if (rc != ERROR_SUCCESS) {
2750 WCMD_print_error();
2751 errorlevel = 2;
2752 } else {
2753 WCMD_output_asis(command);
2754 WCMD_output_asis(equalW);
2755 WCMD_output_asis(newValue);
2756 WCMD_output_asis(newline);
2762 /* Clean up */
2763 RegCloseKey(key);
2766 /****************************************************************************
2767 * WCMD_color
2769 * Clear the terminal screen.
2772 void WCMD_color (void) {
2774 /* Emulate by filling the screen from the top left to bottom right with
2775 spaces, then moving the cursor to the top left afterwards */
2776 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2777 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2779 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2780 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2781 return;
2784 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2786 COORD topLeft;
2787 DWORD screenSize;
2788 DWORD color = 0;
2790 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2792 topLeft.X = 0;
2793 topLeft.Y = 0;
2795 /* Convert the color hex digits */
2796 if (param1[0] == 0x00) {
2797 color = defaultColor;
2798 } else {
2799 color = strtoulW(param1, NULL, 16);
2802 /* Fail if fg == bg color */
2803 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2804 errorlevel = 1;
2805 return;
2808 /* Set the current screen contents and ensure all future writes
2809 remain this color */
2810 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2811 SetConsoleTextAttribute(hStdOut, color);