winebuild/winegcc: Allow building .dll.so without .spec file.
[wine/hacks.git] / programs / cmd / builtins.c
blob8049ed84d8bc76124048f406c3f0b504bc739eaa
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 BOOL copyFromDir = FALSE;
179 WCHAR srcspec[MAX_PATH];
180 DWORD attribs;
181 WCHAR drive[10];
182 WCHAR dir[MAX_PATH];
183 WCHAR fname[MAX_PATH];
184 WCHAR ext[MAX_PATH];
186 if (param1[0] == 0x00) {
187 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
188 return;
191 /* Convert source into full spec */
192 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
193 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
194 if (srcpath[strlenW(srcpath) - 1] == '\\')
195 srcpath[strlenW(srcpath) - 1] = '\0';
197 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
198 attribs = GetFileAttributesW(srcpath);
199 } else {
200 attribs = 0;
202 strcpyW(srcspec, srcpath);
204 /* If a directory, then add \* on the end when searching */
205 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
206 strcatW(srcpath, slashW);
207 copyFromDir = TRUE;
208 strcatW(srcspec, slashW);
209 strcatW(srcspec, starW);
210 } else {
211 WCMD_splitpath(srcpath, drive, dir, fname, ext);
212 strcpyW(srcpath, drive);
213 strcatW(srcpath, dir);
216 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
218 /* If no destination supplied, assume current directory */
219 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
220 if (param2[0] == 0x00) {
221 strcpyW(param2, dotW);
224 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
225 if (outpath[strlenW(outpath) - 1] == '\\')
226 outpath[strlenW(outpath) - 1] = '\0';
227 attribs = GetFileAttributesW(outpath);
228 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
229 strcatW (outpath, slashW);
230 copyToDir = TRUE;
232 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
233 wine_dbgstr_w(outpath), copyToDir);
235 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
236 if (strstrW (quals, parmNoY))
237 force = FALSE;
238 else if (strstrW (quals, parmY))
239 force = TRUE;
240 else {
241 /* By default, we will force the overwrite in batch mode and ask for
242 * confirmation in interactive mode. */
243 force = !!context;
245 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
246 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
247 * default behavior. */
248 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
249 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
250 if (!lstrcmpiW (copycmd, parmY))
251 force = TRUE;
252 else if (!lstrcmpiW (copycmd, parmNoY))
253 force = FALSE;
257 /* Loop through all source files */
258 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
259 hff = FindFirstFileW(srcspec, &fd);
260 if (hff != INVALID_HANDLE_VALUE) {
261 do {
262 WCHAR outname[MAX_PATH];
263 WCHAR srcname[MAX_PATH];
264 BOOL overwrite = force;
266 /* Destination is either supplied filename, or source name in
267 supplied destination directory */
268 strcpyW(outname, outpath);
269 if (copyToDir) strcatW(outname, fd.cFileName);
270 strcpyW(srcname, srcpath);
271 strcatW(srcname, fd.cFileName);
273 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
274 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
276 /* Skip . and .., and directories */
277 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
278 overwrite = FALSE;
279 WINE_TRACE("Skipping directories\n");
282 /* Prompt before overwriting */
283 else if (!overwrite) {
284 attribs = GetFileAttributesW(outname);
285 if (attribs != INVALID_FILE_ATTRIBUTES) {
286 WCHAR buffer[MAXSTRING];
287 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
288 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
290 else overwrite = TRUE;
293 /* Do the copy as appropriate */
294 if (overwrite) {
295 status = CopyFileW(srcname, outname, FALSE);
296 if (!status) WCMD_print_error ();
299 } while (FindNextFileW(hff, &fd) != 0);
300 FindClose (hff);
301 } else {
302 status = ERROR_FILE_NOT_FOUND;
303 WCMD_print_error ();
307 /****************************************************************************
308 * WCMD_create_dir
310 * Create a directory.
312 * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
313 * they do not already exist.
316 static BOOL create_full_path(WCHAR* path)
318 int len;
319 WCHAR *new_path;
320 BOOL ret = TRUE;
322 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path)+1) * sizeof(WCHAR));
323 strcpyW(new_path,path);
325 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
326 new_path[len - 1] = 0;
328 while (!CreateDirectoryW(new_path,NULL))
330 WCHAR *slash;
331 DWORD last_error = GetLastError();
332 if (last_error == ERROR_ALREADY_EXISTS)
333 break;
335 if (last_error != ERROR_PATH_NOT_FOUND)
337 ret = FALSE;
338 break;
341 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
343 ret = FALSE;
344 break;
347 len = slash - new_path;
348 new_path[len] = 0;
349 if (!create_full_path(new_path))
351 ret = FALSE;
352 break;
354 new_path[len] = '\\';
356 HeapFree(GetProcessHeap(),0,new_path);
357 return ret;
360 void WCMD_create_dir (void) {
362 if (param1[0] == 0x00) {
363 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
364 return;
366 if (!create_full_path(param1)) WCMD_print_error ();
369 /****************************************************************************
370 * WCMD_delete
372 * Delete a file or wildcarded set.
374 * Note on /A:
375 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
376 * - Each set is a pattern, eg /ahr /as-r means
377 * readonly+hidden OR nonreadonly system files
378 * - The '-' applies to a single field, ie /a:-hr means read only
379 * non-hidden files
382 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
384 int argno = 0;
385 int argsProcessed = 0;
386 WCHAR *argN = command;
387 BOOL foundAny = FALSE;
388 static const WCHAR parmA[] = {'/','A','\0'};
389 static const WCHAR parmQ[] = {'/','Q','\0'};
390 static const WCHAR parmP[] = {'/','P','\0'};
391 static const WCHAR parmS[] = {'/','S','\0'};
392 static const WCHAR parmF[] = {'/','F','\0'};
394 /* If not recursing, clear error flag */
395 if (expectDir) errorlevel = 0;
397 /* Loop through all args */
398 while (argN) {
399 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
400 WCHAR argCopy[MAX_PATH];
402 if (argN && argN[0] != '/') {
404 WIN32_FIND_DATAW fd;
405 HANDLE hff;
406 WCHAR fpath[MAX_PATH];
407 WCHAR *p;
408 BOOL handleParm = TRUE;
409 BOOL found = FALSE;
410 static const WCHAR anyExt[]= {'.','*','\0'};
412 strcpyW(argCopy, thisArg);
413 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
414 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
415 argsProcessed++;
417 /* If filename part of parameter is * or *.*, prompt unless
418 /Q supplied. */
419 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
421 WCHAR drive[10];
422 WCHAR dir[MAX_PATH];
423 WCHAR fname[MAX_PATH];
424 WCHAR ext[MAX_PATH];
426 /* Convert path into actual directory spec */
427 GetFullPathNameW(argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
428 WCMD_splitpath(fpath, drive, dir, fname, ext);
430 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
431 if ((strcmpW(fname, starW) == 0) &&
432 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
433 BOOL ok;
434 WCHAR question[MAXSTRING];
435 static const WCHAR fmt[] = {'%','s',' ','\0'};
437 /* Note: Flag as found, to avoid file not found message */
438 found = TRUE;
440 /* Ask for confirmation */
441 wsprintfW(question, fmt, fpath);
442 ok = WCMD_ask_confirm(question, TRUE, NULL);
444 /* Abort if answer is 'N' */
445 if (!ok) continue;
449 /* First, try to delete in the current directory */
450 hff = FindFirstFileW(argCopy, &fd);
451 if (hff == INVALID_HANDLE_VALUE) {
452 handleParm = FALSE;
453 } else {
454 found = TRUE;
457 /* Support del <dirname> by just deleting all files dirname\* */
458 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
459 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
460 WCHAR modifiedParm[MAX_PATH];
461 static const WCHAR slashStar[] = {'\\','*','\0'};
463 strcpyW(modifiedParm, argCopy);
464 strcatW(modifiedParm, slashStar);
465 FindClose(hff);
466 found = TRUE;
467 WCMD_delete(modifiedParm, FALSE);
469 } else if (handleParm) {
471 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
472 strcpyW (fpath, argCopy);
473 do {
474 p = strrchrW (fpath, '\\');
475 if (p != NULL) {
476 *++p = '\0';
477 strcatW (fpath, fd.cFileName);
479 else strcpyW (fpath, fd.cFileName);
480 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
481 BOOL ok = TRUE;
482 WCHAR *nextA = strstrW (quals, parmA);
484 /* Handle attribute matching (/A) */
485 if (nextA != NULL) {
486 ok = FALSE;
487 while (nextA != NULL && !ok) {
489 WCHAR *thisA = (nextA+2);
490 BOOL stillOK = TRUE;
492 /* Skip optional : */
493 if (*thisA == ':') thisA++;
495 /* Parse each of the /A[:]xxx in turn */
496 while (*thisA && *thisA != '/') {
497 BOOL negate = FALSE;
498 BOOL attribute = FALSE;
500 /* Match negation of attribute first */
501 if (*thisA == '-') {
502 negate=TRUE;
503 thisA++;
506 /* Match attribute */
507 switch (*thisA) {
508 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
509 break;
510 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
511 break;
512 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
513 break;
514 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
515 break;
516 default:
517 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
520 /* Now check result, keeping a running boolean about whether it
521 matches all parsed attributes so far */
522 if (attribute && !negate) {
523 stillOK = stillOK;
524 } else if (!attribute && negate) {
525 stillOK = stillOK;
526 } else {
527 stillOK = FALSE;
529 thisA++;
532 /* Save the running total as the final result */
533 ok = stillOK;
535 /* Step on to next /A set */
536 nextA = strstrW (nextA+1, parmA);
540 /* /P means prompt for each file */
541 if (ok && strstrW (quals, parmP) != NULL) {
542 WCHAR question[MAXSTRING];
544 /* Ask for confirmation */
545 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
546 ok = WCMD_ask_confirm(question, FALSE, NULL);
549 /* Only proceed if ok to */
550 if (ok) {
552 /* If file is read only, and /F supplied, delete it */
553 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
554 strstrW (quals, parmF) != NULL) {
555 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
558 /* Now do the delete */
559 if (!DeleteFileW(fpath)) WCMD_print_error ();
563 } while (FindNextFileW(hff, &fd) != 0);
564 FindClose (hff);
567 /* Now recurse into all subdirectories handling the parameter in the same way */
568 if (strstrW (quals, parmS) != NULL) {
570 WCHAR thisDir[MAX_PATH];
571 int cPos;
573 WCHAR drive[10];
574 WCHAR dir[MAX_PATH];
575 WCHAR fname[MAX_PATH];
576 WCHAR ext[MAX_PATH];
578 /* Convert path into actual directory spec */
579 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
580 WCMD_splitpath(thisDir, drive, dir, fname, ext);
582 strcpyW(thisDir, drive);
583 strcatW(thisDir, dir);
584 cPos = strlenW(thisDir);
586 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
588 /* Append '*' to the directory */
589 thisDir[cPos] = '*';
590 thisDir[cPos+1] = 0x00;
592 hff = FindFirstFileW(thisDir, &fd);
594 /* Remove residual '*' */
595 thisDir[cPos] = 0x00;
597 if (hff != INVALID_HANDLE_VALUE) {
598 DIRECTORY_STACK *allDirs = NULL;
599 DIRECTORY_STACK *lastEntry = NULL;
601 do {
602 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
603 (strcmpW(fd.cFileName, dotdotW) != 0) &&
604 (strcmpW(fd.cFileName, dotW) != 0)) {
606 DIRECTORY_STACK *nextDir;
607 WCHAR subParm[MAX_PATH];
609 /* Work out search parameter in sub dir */
610 strcpyW (subParm, thisDir);
611 strcatW (subParm, fd.cFileName);
612 strcatW (subParm, slashW);
613 strcatW (subParm, fname);
614 strcatW (subParm, ext);
615 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
617 /* Allocate memory, add to list */
618 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
619 if (allDirs == NULL) allDirs = nextDir;
620 if (lastEntry != NULL) lastEntry->next = nextDir;
621 lastEntry = nextDir;
622 nextDir->next = NULL;
623 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
624 (strlenW(subParm)+1) * sizeof(WCHAR));
625 strcpyW(nextDir->dirName, subParm);
627 } while (FindNextFileW(hff, &fd) != 0);
628 FindClose (hff);
630 /* Go through each subdir doing the delete */
631 while (allDirs != NULL) {
632 DIRECTORY_STACK *tempDir;
634 tempDir = allDirs->next;
635 found |= WCMD_delete (allDirs->dirName, FALSE);
637 HeapFree(GetProcessHeap(),0,allDirs->dirName);
638 HeapFree(GetProcessHeap(),0,allDirs);
639 allDirs = tempDir;
643 /* Keep running total to see if any found, and if not recursing
644 issue error message */
645 if (expectDir) {
646 if (!found) {
647 errorlevel = 1;
648 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
651 foundAny |= found;
655 /* Handle no valid args */
656 if (argsProcessed == 0) {
657 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
660 return foundAny;
663 /****************************************************************************
664 * WCMD_echo
666 * Echo input to the screen (or not). We don't try to emulate the bugs
667 * in DOS (try typing "ECHO ON AGAIN" for an example).
670 void WCMD_echo (const WCHAR *command) {
672 int count;
674 if ((command[0] == '.') && (command[1] == 0)) {
675 WCMD_output (newline);
676 return;
678 if (command[0]==' ')
679 command++;
680 count = strlenW(command);
681 if (count == 0) {
682 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
683 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
684 return;
686 if (lstrcmpiW(command, onW) == 0) {
687 echo_mode = 1;
688 return;
690 if (lstrcmpiW(command, offW) == 0) {
691 echo_mode = 0;
692 return;
694 WCMD_output_asis (command);
695 WCMD_output (newline);
699 /**************************************************************************
700 * WCMD_for
702 * Batch file loop processing.
704 * On entry: cmdList contains the syntax up to the set
705 * next cmdList and all in that bracket contain the set data
706 * next cmdlist contains the DO cmd
707 * following that is either brackets or && entries (as per if)
711 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
713 WIN32_FIND_DATAW fd;
714 HANDLE hff;
715 int i;
716 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
717 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
718 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
719 WCHAR variable[4];
720 WCHAR *firstCmd;
721 int thisDepth;
723 WCHAR *curPos = p;
724 BOOL expandDirs = FALSE;
725 BOOL useNumbers = FALSE;
726 BOOL doRecursive = FALSE;
727 BOOL doFileset = FALSE;
728 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
729 int itemNum;
730 CMD_LIST *thisCmdStart;
733 /* Handle optional qualifiers (multiple are allowed) */
734 while (*curPos && *curPos == '/') {
735 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
736 curPos++;
737 switch (toupperW(*curPos)) {
738 case 'D': curPos++; expandDirs = TRUE; break;
739 case 'L': curPos++; useNumbers = TRUE; break;
741 /* Recursive is special case - /R can have an optional path following it */
742 /* filenamesets are another special case - /F can have an optional options following it */
743 case 'R':
744 case 'F':
746 BOOL isRecursive = (*curPos == 'R');
748 if (isRecursive) doRecursive = TRUE;
749 else doFileset = TRUE;
751 /* Skip whitespace */
752 curPos++;
753 while (*curPos && *curPos==' ') curPos++;
755 /* Next parm is either qualifier, path/options or variable -
756 only care about it if it is the path/options */
757 if (*curPos && *curPos != '/' && *curPos != '%') {
758 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
759 else WINE_FIXME("/F needs to handle options\n");
761 break;
763 default:
764 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
765 curPos++;
768 /* Skip whitespace between qualifiers */
769 while (*curPos && *curPos==' ') curPos++;
772 /* Skip whitespace before variable */
773 while (*curPos && *curPos==' ') curPos++;
775 /* Ensure line continues with variable */
776 if (!*curPos || *curPos != '%') {
777 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
778 return;
781 /* Variable should follow */
782 i = 0;
783 while (curPos[i] && curPos[i]!=' ') i++;
784 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
785 variable[i] = 0x00;
786 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
787 curPos = &curPos[i];
789 /* Skip whitespace before IN */
790 while (*curPos && *curPos==' ') curPos++;
792 /* Ensure line continues with IN */
793 if (!*curPos || lstrcmpiW (curPos, inW)) {
794 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
795 return;
798 /* Save away where the set of data starts and the variable */
799 thisDepth = (*cmdList)->bracketDepth;
800 *cmdList = (*cmdList)->nextcommand;
801 setStart = (*cmdList);
803 /* Skip until the close bracket */
804 WINE_TRACE("Searching %p as the set\n", *cmdList);
805 while (*cmdList &&
806 (*cmdList)->command != NULL &&
807 (*cmdList)->bracketDepth > thisDepth) {
808 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
809 *cmdList = (*cmdList)->nextcommand;
812 /* Skip the close bracket, if there is one */
813 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
815 /* Syntax error if missing close bracket, or nothing following it
816 and once we have the complete set, we expect a DO */
817 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
818 if ((*cmdList == NULL) ||
819 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
820 (*cmdList)->command, 3, doW, -1) != 2)) {
821 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
822 return;
825 /* Save away the starting position for the commands (and offset for the
826 first one */
827 cmdStart = *cmdList;
828 cmdEnd = *cmdList;
829 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
830 itemNum = 0;
832 thisSet = setStart;
833 /* Loop through all set entries */
834 while (thisSet &&
835 thisSet->command != NULL &&
836 thisSet->bracketDepth >= thisDepth) {
838 /* Loop through all entries on the same line */
839 WCHAR *item;
840 WCHAR *itemStart;
842 WINE_TRACE("Processing for set %p\n", thisSet);
843 i = 0;
844 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
847 * If the parameter within the set has a wildcard then search for matching files
848 * otherwise do a literal substitution.
850 static const WCHAR wildcards[] = {'*','?','\0'};
851 thisCmdStart = cmdStart;
853 itemNum++;
854 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
856 if (!useNumbers && !doFileset) {
857 if (strpbrkW (item, wildcards)) {
858 hff = FindFirstFileW(item, &fd);
859 if (hff != INVALID_HANDLE_VALUE) {
860 do {
861 BOOL isDirectory = FALSE;
863 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
865 /* Handle as files or dirs appropriately, but ignore . and .. */
866 if (isDirectory == expandDirs &&
867 (strcmpW(fd.cFileName, dotdotW) != 0) &&
868 (strcmpW(fd.cFileName, dotW) != 0))
870 thisCmdStart = cmdStart;
871 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
872 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
873 fd.cFileName, FALSE, TRUE);
876 } while (FindNextFileW(hff, &fd) != 0);
877 FindClose (hff);
879 } else {
880 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
883 } else if (useNumbers) {
884 /* Convert the first 3 numbers to signed longs and save */
885 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
886 /* else ignore them! */
888 /* Filesets - either a list of files, or a command to run and parse the output */
889 } else if (doFileset && *itemStart != '"') {
891 HANDLE input;
892 WCHAR temp_file[MAX_PATH];
894 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
895 wine_dbgstr_w(item));
897 /* If backquote or single quote, we need to launch that command
898 and parse the results - use a temporary file */
899 if (*itemStart == '`' || *itemStart == '\'') {
901 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
902 static const WCHAR redirOut[] = {'>','%','s','\0'};
903 static const WCHAR cmdW[] = {'C','M','D','\0'};
905 /* Remove trailing character */
906 itemStart[strlenW(itemStart)-1] = 0x00;
908 /* Get temp filename */
909 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
910 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
912 /* Execute program and redirect output */
913 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
914 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
916 /* Open the file, read line by line and process */
917 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
918 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
919 } else {
921 /* Open the file, read line by line and process */
922 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
923 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
926 /* Process the input file */
927 if (input == INVALID_HANDLE_VALUE) {
928 WCMD_print_error ();
929 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
930 errorlevel = 1;
931 return; /* FOR loop aborts at first failure here */
933 } else {
935 WCHAR buffer[MAXSTRING] = {'\0'};
936 WCHAR *where, *parm;
938 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
940 /* Skip blank lines*/
941 parm = WCMD_parameter (buffer, 0, &where);
942 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
943 wine_dbgstr_w(buffer));
945 if (where) {
946 /* FIXME: The following should be moved into its own routine and
947 reused for the string literal parsing below */
948 thisCmdStart = cmdStart;
949 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
950 cmdEnd = thisCmdStart;
953 buffer[0] = 0x00;
956 CloseHandle (input);
959 /* Delete the temporary file */
960 if (*itemStart == '`' || *itemStart == '\'') {
961 DeleteFileW(temp_file);
964 /* Filesets - A string literal */
965 } else if (doFileset && *itemStart == '"') {
966 WCHAR buffer[MAXSTRING] = {'\0'};
967 WCHAR *where, *parm;
969 /* Skip blank lines, and re-extract parameter now string has quotes removed */
970 strcpyW(buffer, item);
971 parm = WCMD_parameter (buffer, 0, &where);
972 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
973 wine_dbgstr_w(buffer));
975 if (where) {
976 /* FIXME: The following should be moved into its own routine and
977 reused for the string literal parsing below */
978 thisCmdStart = cmdStart;
979 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
980 cmdEnd = thisCmdStart;
984 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
985 cmdEnd = thisCmdStart;
986 i++;
989 /* Move onto the next set line */
990 thisSet = thisSet->nextcommand;
993 /* If /L is provided, now run the for loop */
994 if (useNumbers) {
995 WCHAR thisNum[20];
996 static const WCHAR fmt[] = {'%','d','\0'};
998 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
999 numbers[0], numbers[2], numbers[1]);
1000 for (i=numbers[0];
1001 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1002 i=i + numbers[1]) {
1004 sprintfW(thisNum, fmt, i);
1005 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1007 thisCmdStart = cmdStart;
1008 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1009 cmdEnd = thisCmdStart;
1013 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1014 all processing, OR it should be pointing to the end of && processing OR
1015 it should be pointing at the NULL end of bracket for the DO. The return
1016 value needs to be the NEXT command to execute, which it either is, or
1017 we need to step over the closing bracket */
1018 *cmdList = cmdEnd;
1019 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1023 /*****************************************************************************
1024 * WCMD_part_execute
1026 * Execute a command, and any && or bracketed follow on to the command. The
1027 * first command to be executed may not be at the front of the
1028 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1030 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1031 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1033 CMD_LIST *curPosition = *cmdList;
1034 int myDepth = (*cmdList)->bracketDepth;
1036 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1037 cmdList, wine_dbgstr_w(firstcmd),
1038 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1039 conditionTRUE);
1041 /* Skip leading whitespace between condition and the command */
1042 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1044 /* Process the first command, if there is one */
1045 if (conditionTRUE && firstcmd && *firstcmd) {
1046 WCHAR *command = WCMD_strdupW(firstcmd);
1047 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1048 HeapFree(GetProcessHeap(), 0, command);
1052 /* If it didn't move the position, step to next command */
1053 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1055 /* Process any other parts of the command */
1056 if (*cmdList) {
1057 BOOL processThese = TRUE;
1059 if (isIF) processThese = conditionTRUE;
1061 while (*cmdList) {
1062 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1064 /* execute all appropriate commands */
1065 curPosition = *cmdList;
1067 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1068 *cmdList,
1069 (*cmdList)->prevDelim,
1070 (*cmdList)->bracketDepth, myDepth);
1072 /* Execute any statements appended to the line */
1073 /* FIXME: Only if previous call worked for && or failed for || */
1074 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1075 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1076 if (processThese && (*cmdList)->command) {
1077 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1078 value, cmdList);
1080 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1082 /* Execute any appended to the statement with (...) */
1083 } else if ((*cmdList)->bracketDepth > myDepth) {
1084 if (processThese) {
1085 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1086 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1088 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1090 /* End of the command - does 'ELSE ' follow as the next command? */
1091 } else {
1092 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1093 NORM_IGNORECASE | SORT_STRINGSORT,
1094 (*cmdList)->command, 5, ifElse, -1) == 2) {
1096 /* Swap between if and else processing */
1097 processThese = !processThese;
1099 /* Process the ELSE part */
1100 if (processThese) {
1101 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1103 /* Skip leading whitespace between condition and the command */
1104 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1105 if (*cmd) {
1106 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1109 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1110 } else {
1111 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1112 break;
1117 return;
1120 /**************************************************************************
1121 * WCMD_give_help
1123 * Simple on-line help. Help text is stored in the resource file.
1126 void WCMD_give_help (WCHAR *command) {
1128 int i;
1130 command = WCMD_strtrim_leading_spaces(command);
1131 if (strlenW(command) == 0) {
1132 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1134 else {
1135 for (i=0; i<=WCMD_EXIT; i++) {
1136 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1137 command, -1, inbuilt[i], -1) == 2) {
1138 WCMD_output_asis (WCMD_LoadMessage(i));
1139 return;
1142 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1144 return;
1147 /****************************************************************************
1148 * WCMD_go_to
1150 * Batch file jump instruction. Not the most efficient algorithm ;-)
1151 * Prints error message if the specified label cannot be found - the file pointer is
1152 * then at EOF, effectively stopping the batch file.
1153 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1156 void WCMD_goto (CMD_LIST **cmdList) {
1158 WCHAR string[MAX_PATH];
1160 /* Do not process any more parts of a processed multipart or multilines command */
1161 if (cmdList) *cmdList = NULL;
1163 if (param1[0] == 0x00) {
1164 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1165 return;
1167 if (context != NULL) {
1168 WCHAR *paramStart = param1, *str;
1169 static const WCHAR eofW[] = {':','e','o','f','\0'};
1171 /* Handle special :EOF label */
1172 if (lstrcmpiW (eofW, param1) == 0) {
1173 context -> skip_rest = TRUE;
1174 return;
1177 /* Support goto :label as well as goto label */
1178 if (*paramStart == ':') paramStart++;
1180 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1181 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1182 str = string;
1183 while (isspaceW(*str)) str++;
1184 if ((*str == ':') && (lstrcmpiW (++str, paramStart) == 0)) return;
1186 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1188 return;
1191 /*****************************************************************************
1192 * WCMD_pushd
1194 * Push a directory onto the stack
1197 void WCMD_pushd (WCHAR *command) {
1198 struct env_stack *curdir;
1199 WCHAR *thisdir;
1200 static const WCHAR parmD[] = {'/','D','\0'};
1202 if (strchrW(command, '/') != NULL) {
1203 SetLastError(ERROR_INVALID_PARAMETER);
1204 WCMD_print_error();
1205 return;
1208 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1209 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1210 if( !curdir || !thisdir ) {
1211 LocalFree(curdir);
1212 LocalFree(thisdir);
1213 WINE_ERR ("out of memory\n");
1214 return;
1217 /* Change directory using CD code with /D parameter */
1218 strcpyW(quals, parmD);
1219 GetCurrentDirectoryW (1024, thisdir);
1220 errorlevel = 0;
1221 WCMD_setshow_default(command);
1222 if (errorlevel) {
1223 LocalFree(curdir);
1224 LocalFree(thisdir);
1225 return;
1226 } else {
1227 curdir -> next = pushd_directories;
1228 curdir -> strings = thisdir;
1229 if (pushd_directories == NULL) {
1230 curdir -> u.stackdepth = 1;
1231 } else {
1232 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1234 pushd_directories = curdir;
1239 /*****************************************************************************
1240 * WCMD_popd
1242 * Pop a directory from the stack
1245 void WCMD_popd (void) {
1246 struct env_stack *temp = pushd_directories;
1248 if (!pushd_directories)
1249 return;
1251 /* pop the old environment from the stack, and make it the current dir */
1252 pushd_directories = temp->next;
1253 SetCurrentDirectoryW(temp->strings);
1254 LocalFree (temp->strings);
1255 LocalFree (temp);
1258 /****************************************************************************
1259 * WCMD_if
1261 * Batch file conditional.
1263 * On entry, cmdlist will point to command containing the IF, and optionally
1264 * the first command to execute (if brackets not found)
1265 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1266 * If ('s were found, execute all within that bracket
1267 * Command may optionally be followed by an ELSE - need to skip instructions
1268 * in the else using the same logic
1270 * FIXME: Much more syntax checking needed!
1273 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1275 int negate = 0, test = 0;
1276 WCHAR condition[MAX_PATH], *command, *s;
1277 static const WCHAR notW[] = {'n','o','t','\0'};
1278 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1279 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1280 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1281 static const WCHAR eqeqW[] = {'=','=','\0'};
1282 static const WCHAR parmI[] = {'/','I','\0'};
1284 if (!lstrcmpiW (param1, notW)) {
1285 negate = 1;
1286 strcpyW (condition, param2);
1288 else {
1289 strcpyW (condition, param1);
1291 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1293 if (!lstrcmpiW (condition, errlvlW)) {
1294 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1295 WCMD_parameter (p, 2+negate, &command);
1297 else if (!lstrcmpiW (condition, existW)) {
1298 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1299 test = 1;
1301 WCMD_parameter (p, 2+negate, &command);
1303 else if (!lstrcmpiW (condition, defdW)) {
1304 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1305 test = 1;
1307 WCMD_parameter (p, 2+negate, &command);
1309 else if ((s = strstrW (p, eqeqW))) {
1310 s += 2;
1311 if (strstrW (quals, parmI) == NULL) {
1312 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1314 else {
1315 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1317 WCMD_parameter (s, 1, &command);
1319 else {
1320 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1321 return;
1324 /* Process rest of IF statement which is on the same line
1325 Note: This may process all or some of the cmdList (eg a GOTO) */
1326 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1329 /****************************************************************************
1330 * WCMD_move
1332 * Move a file, directory tree or wildcarded set of files.
1335 void WCMD_move (void) {
1337 int status;
1338 WIN32_FIND_DATAW fd;
1339 HANDLE hff;
1340 WCHAR input[MAX_PATH];
1341 WCHAR output[MAX_PATH];
1342 WCHAR drive[10];
1343 WCHAR dir[MAX_PATH];
1344 WCHAR fname[MAX_PATH];
1345 WCHAR ext[MAX_PATH];
1347 if (param1[0] == 0x00) {
1348 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1349 return;
1352 /* If no destination supplied, assume current directory */
1353 if (param2[0] == 0x00) {
1354 strcpyW(param2, dotW);
1357 /* If 2nd parm is directory, then use original filename */
1358 /* Convert partial path to full path */
1359 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1360 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1361 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1362 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1364 /* Split into components */
1365 WCMD_splitpath(input, drive, dir, fname, ext);
1367 hff = FindFirstFileW(input, &fd);
1368 while (hff != INVALID_HANDLE_VALUE) {
1369 WCHAR dest[MAX_PATH];
1370 WCHAR src[MAX_PATH];
1371 DWORD attribs;
1373 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1375 /* Build src & dest name */
1376 strcpyW(src, drive);
1377 strcatW(src, dir);
1379 /* See if dest is an existing directory */
1380 attribs = GetFileAttributesW(output);
1381 if (attribs != INVALID_FILE_ATTRIBUTES &&
1382 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1383 strcpyW(dest, output);
1384 strcatW(dest, slashW);
1385 strcatW(dest, fd.cFileName);
1386 } else {
1387 strcpyW(dest, output);
1390 strcatW(src, fd.cFileName);
1392 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1393 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1395 /* Check if file is read only, otherwise move it */
1396 attribs = GetFileAttributesW(src);
1397 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1398 (attribs & FILE_ATTRIBUTE_READONLY)) {
1399 SetLastError(ERROR_ACCESS_DENIED);
1400 status = 0;
1401 } else {
1402 BOOL ok = TRUE;
1404 /* If destination exists, prompt unless /Y supplied */
1405 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1406 BOOL force = FALSE;
1407 WCHAR copycmd[MAXSTRING];
1408 int len;
1410 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1411 if (strstrW (quals, parmNoY))
1412 force = FALSE;
1413 else if (strstrW (quals, parmY))
1414 force = TRUE;
1415 else {
1416 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1417 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1418 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1419 && ! lstrcmpiW (copycmd, parmY));
1422 /* Prompt if overwriting */
1423 if (!force) {
1424 WCHAR question[MAXSTRING];
1425 WCHAR yesChar[10];
1427 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1429 /* Ask for confirmation */
1430 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1431 ok = WCMD_ask_confirm(question, FALSE, NULL);
1433 /* So delete the destination prior to the move */
1434 if (ok) {
1435 if (!DeleteFileW(dest)) {
1436 WCMD_print_error ();
1437 errorlevel = 1;
1438 ok = FALSE;
1444 if (ok) {
1445 status = MoveFileW(src, dest);
1446 } else {
1447 status = 1; /* Anything other than 0 to prevent error msg below */
1451 if (!status) {
1452 WCMD_print_error ();
1453 errorlevel = 1;
1456 /* Step on to next match */
1457 if (FindNextFileW(hff, &fd) == 0) {
1458 FindClose(hff);
1459 hff = INVALID_HANDLE_VALUE;
1460 break;
1465 /****************************************************************************
1466 * WCMD_pause
1468 * Wait for keyboard input.
1471 void WCMD_pause (void) {
1473 DWORD count;
1474 WCHAR string[32];
1476 WCMD_output (anykey);
1477 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1478 sizeof(string)/sizeof(WCHAR), &count, NULL);
1481 /****************************************************************************
1482 * WCMD_remove_dir
1484 * Delete a directory.
1487 void WCMD_remove_dir (WCHAR *command) {
1489 int argno = 0;
1490 int argsProcessed = 0;
1491 WCHAR *argN = command;
1492 static const WCHAR parmS[] = {'/','S','\0'};
1493 static const WCHAR parmQ[] = {'/','Q','\0'};
1495 /* Loop through all args */
1496 while (argN) {
1497 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1498 if (argN && argN[0] != '/') {
1499 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1500 wine_dbgstr_w(quals));
1501 argsProcessed++;
1503 /* If subdirectory search not supplied, just try to remove
1504 and report error if it fails (eg if it contains a file) */
1505 if (strstrW (quals, parmS) == NULL) {
1506 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1508 /* Otherwise use ShFileOp to recursively remove a directory */
1509 } else {
1511 SHFILEOPSTRUCTW lpDir;
1513 /* Ask first */
1514 if (strstrW (quals, parmQ) == NULL) {
1515 BOOL ok;
1516 WCHAR question[MAXSTRING];
1517 static const WCHAR fmt[] = {'%','s',' ','\0'};
1519 /* Ask for confirmation */
1520 wsprintfW(question, fmt, thisArg);
1521 ok = WCMD_ask_confirm(question, TRUE, NULL);
1523 /* Abort if answer is 'N' */
1524 if (!ok) return;
1527 /* Do the delete */
1528 lpDir.hwnd = NULL;
1529 lpDir.pTo = NULL;
1530 lpDir.pFrom = thisArg;
1531 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1532 lpDir.wFunc = FO_DELETE;
1533 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1538 /* Handle no valid args */
1539 if (argsProcessed == 0) {
1540 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1541 return;
1546 /****************************************************************************
1547 * WCMD_rename
1549 * Rename a file.
1552 void WCMD_rename (void) {
1554 int status;
1555 HANDLE hff;
1556 WIN32_FIND_DATAW fd;
1557 WCHAR input[MAX_PATH];
1558 WCHAR *dotDst = NULL;
1559 WCHAR drive[10];
1560 WCHAR dir[MAX_PATH];
1561 WCHAR fname[MAX_PATH];
1562 WCHAR ext[MAX_PATH];
1563 DWORD attribs;
1565 errorlevel = 0;
1567 /* Must be at least two args */
1568 if (param1[0] == 0x00 || param2[0] == 0x00) {
1569 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1570 errorlevel = 1;
1571 return;
1574 /* Destination cannot contain a drive letter or directory separator */
1575 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1576 SetLastError(ERROR_INVALID_PARAMETER);
1577 WCMD_print_error();
1578 errorlevel = 1;
1579 return;
1582 /* Convert partial path to full path */
1583 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1584 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1585 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1586 dotDst = strchrW(param2, '.');
1588 /* Split into components */
1589 WCMD_splitpath(input, drive, dir, fname, ext);
1591 hff = FindFirstFileW(input, &fd);
1592 while (hff != INVALID_HANDLE_VALUE) {
1593 WCHAR dest[MAX_PATH];
1594 WCHAR src[MAX_PATH];
1595 WCHAR *dotSrc = NULL;
1596 int dirLen;
1598 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1600 /* FIXME: If dest name or extension is *, replace with filename/ext
1601 part otherwise use supplied name. This supports:
1602 ren *.fred *.jim
1603 ren jim.* fred.* etc
1604 However, windows has a more complex algorithm supporting eg
1605 ?'s and *'s mid name */
1606 dotSrc = strchrW(fd.cFileName, '.');
1608 /* Build src & dest name */
1609 strcpyW(src, drive);
1610 strcatW(src, dir);
1611 strcpyW(dest, src);
1612 dirLen = strlenW(src);
1613 strcatW(src, fd.cFileName);
1615 /* Build name */
1616 if (param2[0] == '*') {
1617 strcatW(dest, fd.cFileName);
1618 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1619 } else {
1620 strcatW(dest, param2);
1621 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1624 /* Build Extension */
1625 if (dotDst && (*(dotDst+1)=='*')) {
1626 if (dotSrc) strcatW(dest, dotSrc);
1627 } else if (dotDst) {
1628 if (dotDst) strcatW(dest, dotDst);
1631 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1632 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1634 /* Check if file is read only, otherwise move it */
1635 attribs = GetFileAttributesW(src);
1636 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1637 (attribs & FILE_ATTRIBUTE_READONLY)) {
1638 SetLastError(ERROR_ACCESS_DENIED);
1639 status = 0;
1640 } else {
1641 status = MoveFileW(src, dest);
1644 if (!status) {
1645 WCMD_print_error ();
1646 errorlevel = 1;
1649 /* Step on to next match */
1650 if (FindNextFileW(hff, &fd) == 0) {
1651 FindClose(hff);
1652 hff = INVALID_HANDLE_VALUE;
1653 break;
1658 /*****************************************************************************
1659 * WCMD_dupenv
1661 * Make a copy of the environment.
1663 static WCHAR *WCMD_dupenv( const WCHAR *env )
1665 WCHAR *env_copy;
1666 int len;
1668 if( !env )
1669 return NULL;
1671 len = 0;
1672 while ( env[len] )
1673 len += (strlenW(&env[len]) + 1);
1675 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1676 if (!env_copy)
1678 WINE_ERR("out of memory\n");
1679 return env_copy;
1681 memcpy (env_copy, env, len*sizeof (WCHAR));
1682 env_copy[len] = 0;
1684 return env_copy;
1687 /*****************************************************************************
1688 * WCMD_setlocal
1690 * setlocal pushes the environment onto a stack
1691 * Save the environment as unicode so we don't screw anything up.
1693 void WCMD_setlocal (const WCHAR *s) {
1694 WCHAR *env;
1695 struct env_stack *env_copy;
1696 WCHAR cwd[MAX_PATH];
1698 /* DISABLEEXTENSIONS ignored */
1700 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1701 if( !env_copy )
1703 WINE_ERR ("out of memory\n");
1704 return;
1707 env = GetEnvironmentStringsW ();
1709 env_copy->strings = WCMD_dupenv (env);
1710 if (env_copy->strings)
1712 env_copy->next = saved_environment;
1713 saved_environment = env_copy;
1715 /* Save the current drive letter */
1716 GetCurrentDirectoryW(MAX_PATH, cwd);
1717 env_copy->u.cwd = cwd[0];
1719 else
1720 LocalFree (env_copy);
1722 FreeEnvironmentStringsW (env);
1726 /*****************************************************************************
1727 * WCMD_endlocal
1729 * endlocal pops the environment off a stack
1730 * Note: When searching for '=', search from WCHAR position 1, to handle
1731 * special internal environment variables =C:, =D: etc
1733 void WCMD_endlocal (void) {
1734 WCHAR *env, *old, *p;
1735 struct env_stack *temp;
1736 int len, n;
1738 if (!saved_environment)
1739 return;
1741 /* pop the old environment from the stack */
1742 temp = saved_environment;
1743 saved_environment = temp->next;
1745 /* delete the current environment, totally */
1746 env = GetEnvironmentStringsW ();
1747 old = WCMD_dupenv (GetEnvironmentStringsW ());
1748 len = 0;
1749 while (old[len]) {
1750 n = strlenW(&old[len]) + 1;
1751 p = strchrW(&old[len] + 1, '=');
1752 if (p)
1754 *p++ = 0;
1755 SetEnvironmentVariableW (&old[len], NULL);
1757 len += n;
1759 LocalFree (old);
1760 FreeEnvironmentStringsW (env);
1762 /* restore old environment */
1763 env = temp->strings;
1764 len = 0;
1765 while (env[len]) {
1766 n = strlenW(&env[len]) + 1;
1767 p = strchrW(&env[len] + 1, '=');
1768 if (p)
1770 *p++ = 0;
1771 SetEnvironmentVariableW (&env[len], p);
1773 len += n;
1776 /* Restore current drive letter */
1777 if (IsCharAlphaW(temp->u.cwd)) {
1778 WCHAR envvar[4];
1779 WCHAR cwd[MAX_PATH];
1780 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1782 wsprintfW(envvar, fmt, temp->u.cwd);
1783 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1784 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1785 SetCurrentDirectoryW(cwd);
1789 LocalFree (env);
1790 LocalFree (temp);
1793 /*****************************************************************************
1794 * WCMD_setshow_attrib
1796 * Display and optionally sets DOS attributes on a file or directory
1800 void WCMD_setshow_attrib (void) {
1802 DWORD count;
1803 HANDLE hff;
1804 WIN32_FIND_DATAW fd;
1805 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1806 WCHAR *name = param1;
1807 DWORD attrib_set=0;
1808 DWORD attrib_clear=0;
1810 if (param1[0] == '+' || param1[0] == '-') {
1811 DWORD attrib = 0;
1812 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
1813 switch (param1[1]) {
1814 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
1815 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
1816 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
1817 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
1818 default:
1819 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1820 return;
1822 switch (param1[0]) {
1823 case '+': attrib_set = attrib; break;
1824 case '-': attrib_clear = attrib; break;
1826 name = param2;
1829 if (strlenW(name) == 0) {
1830 static const WCHAR slashStarW[] = {'\\','*','\0'};
1832 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
1833 strcatW (name, slashStarW);
1836 hff = FindFirstFileW(name, &fd);
1837 if (hff == INVALID_HANDLE_VALUE) {
1838 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
1840 else {
1841 do {
1842 if (attrib_set || attrib_clear) {
1843 fd.dwFileAttributes &= ~attrib_clear;
1844 fd.dwFileAttributes |= attrib_set;
1845 if (!fd.dwFileAttributes)
1846 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
1847 SetFileAttributesW(name, fd.dwFileAttributes);
1848 } else {
1849 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1850 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1851 flags[0] = 'H';
1853 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1854 flags[1] = 'S';
1856 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1857 flags[2] = 'A';
1859 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1860 flags[3] = 'R';
1862 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1863 flags[4] = 'T';
1865 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1866 flags[5] = 'C';
1868 WCMD_output (fmt, flags, fd.cFileName);
1869 for (count=0; count < 8; count++) flags[count] = ' ';
1871 } while (FindNextFileW(hff, &fd) != 0);
1873 FindClose (hff);
1876 /*****************************************************************************
1877 * WCMD_setshow_default
1879 * Set/Show the current default directory
1882 void WCMD_setshow_default (WCHAR *command) {
1884 BOOL status;
1885 WCHAR string[1024];
1886 WCHAR cwd[1024];
1887 WCHAR *pos;
1888 WIN32_FIND_DATAW fd;
1889 HANDLE hff;
1890 static const WCHAR parmD[] = {'/','D','\0'};
1892 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1894 /* Skip /D and trailing whitespace if on the front of the command line */
1895 if (CompareStringW(LOCALE_USER_DEFAULT,
1896 NORM_IGNORECASE | SORT_STRINGSORT,
1897 command, 2, parmD, -1) == 2) {
1898 command += 2;
1899 while (*command && *command==' ') command++;
1902 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
1903 if (strlenW(command) == 0) {
1904 strcatW (cwd, newline);
1905 WCMD_output (cwd);
1907 else {
1908 /* Remove any double quotes, which may be in the
1909 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1910 pos = string;
1911 while (*command) {
1912 if (*command != '"') *pos++ = *command;
1913 command++;
1915 *pos = 0x00;
1917 /* Search for appropriate directory */
1918 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1919 hff = FindFirstFileW(string, &fd);
1920 while (hff != INVALID_HANDLE_VALUE) {
1921 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1922 WCHAR fpath[MAX_PATH];
1923 WCHAR drive[10];
1924 WCHAR dir[MAX_PATH];
1925 WCHAR fname[MAX_PATH];
1926 WCHAR ext[MAX_PATH];
1927 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1929 /* Convert path into actual directory spec */
1930 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1931 WCMD_splitpath(fpath, drive, dir, fname, ext);
1933 /* Rebuild path */
1934 wsprintfW(string, fmt, drive, dir, fd.cFileName);
1936 FindClose(hff);
1937 hff = INVALID_HANDLE_VALUE;
1938 break;
1941 /* Step on to next match */
1942 if (FindNextFileW(hff, &fd) == 0) {
1943 FindClose(hff);
1944 hff = INVALID_HANDLE_VALUE;
1945 break;
1949 /* Change to that directory */
1950 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1952 status = SetCurrentDirectoryW(string);
1953 if (!status) {
1954 errorlevel = 1;
1955 WCMD_print_error ();
1956 return;
1957 } else {
1959 /* Save away the actual new directory, to store as current location */
1960 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
1962 /* Restore old directory if drive letter would change, and
1963 CD x:\directory /D (or pushd c:\directory) not supplied */
1964 if ((strstrW(quals, parmD) == NULL) &&
1965 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1966 SetCurrentDirectoryW(cwd);
1970 /* Set special =C: type environment variable, for drive letter of
1971 change of directory, even if path was restored due to missing
1972 /D (allows changing drive letter when not resident on that
1973 drive */
1974 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
1975 WCHAR env[4];
1976 strcpyW(env, equalW);
1977 memcpy(env+1, string, 2 * sizeof(WCHAR));
1978 env[3] = 0x00;
1979 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1980 SetEnvironmentVariableW(env, string);
1984 return;
1987 /****************************************************************************
1988 * WCMD_setshow_date
1990 * Set/Show the system date
1991 * FIXME: Can't change date yet
1994 void WCMD_setshow_date (void) {
1996 WCHAR curdate[64], buffer[64];
1997 DWORD count;
1998 static const WCHAR parmT[] = {'/','T','\0'};
2000 if (strlenW(param1) == 0) {
2001 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2002 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2003 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2004 if (strstrW (quals, parmT) == NULL) {
2005 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2006 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2007 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2008 if (count > 2) {
2009 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2013 else WCMD_print_error ();
2015 else {
2016 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2020 /****************************************************************************
2021 * WCMD_compare
2023 static int WCMD_compare( const void *a, const void *b )
2025 int r;
2026 const WCHAR * const *str_a = a, * const *str_b = b;
2027 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2028 *str_a, -1, *str_b, -1 );
2029 if( r == CSTR_LESS_THAN ) return -1;
2030 if( r == CSTR_GREATER_THAN ) return 1;
2031 return 0;
2034 /****************************************************************************
2035 * WCMD_setshow_sortenv
2037 * sort variables into order for display
2038 * Optionally only display those who start with a stub
2039 * returns the count displayed
2041 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2043 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2044 const WCHAR **str;
2046 if (stub) stublen = strlenW(stub);
2048 /* count the number of strings, and the total length */
2049 while ( s[len] ) {
2050 len += (strlenW(&s[len]) + 1);
2051 count++;
2054 /* add the strings to an array */
2055 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2056 if( !str )
2057 return 0;
2058 str[0] = s;
2059 for( i=1; i<count; i++ )
2060 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2062 /* sort the array */
2063 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2065 /* print it */
2066 for( i=0; i<count; i++ ) {
2067 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2068 NORM_IGNORECASE | SORT_STRINGSORT,
2069 str[i], stublen, stub, -1) == 2) {
2070 /* Don't display special internal variables */
2071 if (str[i][0] != '=') {
2072 WCMD_output_asis(str[i]);
2073 WCMD_output_asis(newline);
2074 displayedcount++;
2079 LocalFree( str );
2080 return displayedcount;
2083 /****************************************************************************
2084 * WCMD_setshow_env
2086 * Set/Show the environment variables
2089 void WCMD_setshow_env (WCHAR *s) {
2091 LPVOID env;
2092 WCHAR *p;
2093 int status;
2094 static const WCHAR parmP[] = {'/','P','\0'};
2096 errorlevel = 0;
2097 if (param1[0] == 0x00 && quals[0] == 0x00) {
2098 env = GetEnvironmentStringsW();
2099 WCMD_setshow_sortenv( env, NULL );
2100 return;
2103 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2104 if (CompareStringW(LOCALE_USER_DEFAULT,
2105 NORM_IGNORECASE | SORT_STRINGSORT,
2106 s, 2, parmP, -1) == 2) {
2107 WCHAR string[MAXSTRING];
2108 DWORD count;
2110 s += 2;
2111 while (*s && *s==' ') s++;
2112 if (*s=='\"')
2113 WCMD_opt_s_strip_quotes(s);
2115 /* If no parameter, or no '=' sign, return an error */
2116 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2117 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2118 return;
2121 /* Output the prompt */
2122 *p++ = '\0';
2123 if (strlenW(p) != 0) WCMD_output(p);
2125 /* Read the reply */
2126 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2127 sizeof(string)/sizeof(WCHAR), &count, NULL);
2128 if (count > 1) {
2129 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2130 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2131 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2132 wine_dbgstr_w(string));
2133 status = SetEnvironmentVariableW(s, string);
2136 } else {
2137 DWORD gle;
2139 if (*s=='\"')
2140 WCMD_opt_s_strip_quotes(s);
2141 p = strchrW (s, '=');
2142 if (p == NULL) {
2143 env = GetEnvironmentStringsW();
2144 if (WCMD_setshow_sortenv( env, s ) == 0) {
2145 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2146 errorlevel = 1;
2148 return;
2150 *p++ = '\0';
2152 if (strlenW(p) == 0) p = NULL;
2153 status = SetEnvironmentVariableW(s, p);
2154 gle = GetLastError();
2155 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2156 errorlevel = 1;
2157 } else if ((!status)) WCMD_print_error();
2161 /****************************************************************************
2162 * WCMD_setshow_path
2164 * Set/Show the path environment variable
2167 void WCMD_setshow_path (WCHAR *command) {
2169 WCHAR string[1024];
2170 DWORD status;
2171 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2172 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2174 if (strlenW(param1) == 0) {
2175 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2176 if (status != 0) {
2177 WCMD_output_asis ( pathEqW);
2178 WCMD_output_asis ( string);
2179 WCMD_output_asis ( newline);
2181 else {
2182 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2185 else {
2186 if (*command == '=') command++; /* Skip leading '=' */
2187 status = SetEnvironmentVariableW(pathW, command);
2188 if (!status) WCMD_print_error();
2192 /****************************************************************************
2193 * WCMD_setshow_prompt
2195 * Set or show the command prompt.
2198 void WCMD_setshow_prompt (void) {
2200 WCHAR *s;
2201 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2203 if (strlenW(param1) == 0) {
2204 SetEnvironmentVariableW(promptW, NULL);
2206 else {
2207 s = param1;
2208 while ((*s == '=') || (*s == ' ')) s++;
2209 if (strlenW(s) == 0) {
2210 SetEnvironmentVariableW(promptW, NULL);
2212 else SetEnvironmentVariableW(promptW, s);
2216 /****************************************************************************
2217 * WCMD_setshow_time
2219 * Set/Show the system time
2220 * FIXME: Can't change time yet
2223 void WCMD_setshow_time (void) {
2225 WCHAR curtime[64], buffer[64];
2226 DWORD count;
2227 SYSTEMTIME st;
2228 static const WCHAR parmT[] = {'/','T','\0'};
2230 if (strlenW(param1) == 0) {
2231 GetLocalTime(&st);
2232 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2233 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2234 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2235 if (strstrW (quals, parmT) == NULL) {
2236 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2237 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2238 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2239 if (count > 2) {
2240 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2244 else WCMD_print_error ();
2246 else {
2247 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2251 /****************************************************************************
2252 * WCMD_shift
2254 * Shift batch parameters.
2255 * Optional /n says where to start shifting (n=0-8)
2258 void WCMD_shift (WCHAR *command) {
2259 int start;
2261 if (context != NULL) {
2262 WCHAR *pos = strchrW(command, '/');
2263 int i;
2265 if (pos == NULL) {
2266 start = 0;
2267 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2268 start = (*(pos+1) - '0');
2269 } else {
2270 SetLastError(ERROR_INVALID_PARAMETER);
2271 WCMD_print_error();
2272 return;
2275 WINE_TRACE("Shifting variables, starting at %d\n", start);
2276 for (i=start;i<=8;i++) {
2277 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2279 context -> shift_count[9] = context -> shift_count[9] + 1;
2284 /****************************************************************************
2285 * WCMD_title
2287 * Set the console title
2289 void WCMD_title (WCHAR *command) {
2290 SetConsoleTitleW(command);
2293 /****************************************************************************
2294 * WCMD_type
2296 * Copy a file to standard output.
2299 void WCMD_type (WCHAR *command) {
2301 int argno = 0;
2302 WCHAR *argN = command;
2303 BOOL writeHeaders = FALSE;
2305 if (param1[0] == 0x00) {
2306 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2307 return;
2310 if (param2[0] != 0x00) writeHeaders = TRUE;
2312 /* Loop through all args */
2313 errorlevel = 0;
2314 while (argN) {
2315 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2317 HANDLE h;
2318 WCHAR buffer[512];
2319 DWORD count;
2321 if (!argN) break;
2323 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2324 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2325 FILE_ATTRIBUTE_NORMAL, NULL);
2326 if (h == INVALID_HANDLE_VALUE) {
2327 WCMD_print_error ();
2328 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2329 errorlevel = 1;
2330 } else {
2331 if (writeHeaders) {
2332 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2333 WCMD_output(fmt, thisArg);
2335 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2336 if (count == 0) break; /* ReadFile reports success on EOF! */
2337 buffer[count] = 0;
2338 WCMD_output_asis (buffer);
2340 CloseHandle (h);
2341 if (!writeHeaders)
2342 WCMD_output_asis (newline);
2347 /****************************************************************************
2348 * WCMD_more
2350 * Output either a file or stdin to screen in pages
2353 void WCMD_more (WCHAR *command) {
2355 int argno = 0;
2356 WCHAR *argN = command;
2357 BOOL useinput = FALSE;
2358 WCHAR moreStr[100];
2359 WCHAR moreStrPage[100];
2360 WCHAR buffer[512];
2361 DWORD count;
2362 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2363 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2364 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2365 ')',' ','-','-','\n','\0'};
2366 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2368 /* Prefix the NLS more with '-- ', then load the text */
2369 errorlevel = 0;
2370 strcpyW(moreStr, moreStart);
2371 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2372 (sizeof(moreStr)/sizeof(WCHAR))-3);
2374 if (param1[0] == 0x00) {
2376 /* Wine implements pipes via temporary files, and hence stdin is
2377 effectively reading from the file. This means the prompts for
2378 more are satisfied by the next line from the input (file). To
2379 avoid this, ensure stdin is to the console */
2380 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2381 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2382 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2383 FILE_ATTRIBUTE_NORMAL, 0);
2384 WINE_TRACE("No parms - working probably in pipe mode\n");
2385 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2387 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2388 once you get in this bit unless due to a pipe, its going to end badly... */
2389 useinput = TRUE;
2390 wsprintfW(moreStrPage, moreFmt, moreStr);
2392 WCMD_enter_paged_mode(moreStrPage);
2393 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2394 if (count == 0) break; /* ReadFile reports success on EOF! */
2395 buffer[count] = 0;
2396 WCMD_output_asis (buffer);
2398 WCMD_leave_paged_mode();
2400 /* Restore stdin to what it was */
2401 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2402 CloseHandle(hConIn);
2404 return;
2405 } else {
2406 BOOL needsPause = FALSE;
2408 /* Loop through all args */
2409 WINE_TRACE("Parms supplied - working through each file\n");
2410 WCMD_enter_paged_mode(moreStrPage);
2412 while (argN) {
2413 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2414 HANDLE h;
2416 if (!argN) break;
2418 if (needsPause) {
2420 /* Wait */
2421 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2422 WCMD_leave_paged_mode();
2423 WCMD_output_asis(moreStrPage);
2424 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2425 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2426 WCMD_enter_paged_mode(moreStrPage);
2430 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2431 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2432 FILE_ATTRIBUTE_NORMAL, NULL);
2433 if (h == INVALID_HANDLE_VALUE) {
2434 WCMD_print_error ();
2435 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2436 errorlevel = 1;
2437 } else {
2438 ULONG64 curPos = 0;
2439 ULONG64 fileLen = 0;
2440 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2442 /* Get the file size */
2443 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2444 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2446 needsPause = TRUE;
2447 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2448 if (count == 0) break; /* ReadFile reports success on EOF! */
2449 buffer[count] = 0;
2450 curPos += count;
2452 /* Update % count (would be used in WCMD_output_asis as prompt) */
2453 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2455 WCMD_output_asis (buffer);
2457 CloseHandle (h);
2461 WCMD_leave_paged_mode();
2465 /****************************************************************************
2466 * WCMD_verify
2468 * Display verify flag.
2469 * FIXME: We don't actually do anything with the verify flag other than toggle
2470 * it...
2473 void WCMD_verify (WCHAR *command) {
2475 int count;
2477 count = strlenW(command);
2478 if (count == 0) {
2479 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2480 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2481 return;
2483 if (lstrcmpiW(command, onW) == 0) {
2484 verify_mode = 1;
2485 return;
2487 else if (lstrcmpiW(command, offW) == 0) {
2488 verify_mode = 0;
2489 return;
2491 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2494 /****************************************************************************
2495 * WCMD_version
2497 * Display version info.
2500 void WCMD_version (void) {
2502 WCMD_output (version_string);
2506 /****************************************************************************
2507 * WCMD_volume
2509 * Display volume info and/or set volume label. Returns 0 if error.
2512 int WCMD_volume (int mode, WCHAR *path) {
2514 DWORD count, serial;
2515 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2516 BOOL status;
2518 if (strlenW(path) == 0) {
2519 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2520 if (!status) {
2521 WCMD_print_error ();
2522 return 0;
2524 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2525 &serial, NULL, NULL, NULL, 0);
2527 else {
2528 static const WCHAR fmt[] = {'%','s','\\','\0'};
2529 if ((path[1] != ':') || (strlenW(path) != 2)) {
2530 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2531 return 0;
2533 wsprintfW (curdir, fmt, path);
2534 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2535 &serial, NULL,
2536 NULL, NULL, 0);
2538 if (!status) {
2539 WCMD_print_error ();
2540 return 0;
2542 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2543 curdir[0], label, HIWORD(serial), LOWORD(serial));
2544 if (mode) {
2545 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2546 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2547 sizeof(string)/sizeof(WCHAR), &count, NULL);
2548 if (count > 1) {
2549 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2550 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2552 if (strlenW(path) != 0) {
2553 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2555 else {
2556 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2559 return 1;
2562 /**************************************************************************
2563 * WCMD_exit
2565 * Exit either the process, or just this batch program
2569 void WCMD_exit (CMD_LIST **cmdList) {
2571 static const WCHAR parmB[] = {'/','B','\0'};
2572 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2574 if (context && lstrcmpiW(quals, parmB) == 0) {
2575 errorlevel = rc;
2576 context -> skip_rest = TRUE;
2577 *cmdList = NULL;
2578 } else {
2579 ExitProcess(rc);
2584 /*****************************************************************************
2585 * WCMD_assoc
2587 * Lists or sets file associations (assoc = TRUE)
2588 * Lists or sets file types (assoc = FALSE)
2590 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2592 HKEY key;
2593 DWORD accessOptions = KEY_READ;
2594 WCHAR *newValue;
2595 LONG rc = ERROR_SUCCESS;
2596 WCHAR keyValue[MAXSTRING];
2597 DWORD valueLen = MAXSTRING;
2598 HKEY readKey;
2599 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2600 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2602 /* See if parameter includes '=' */
2603 errorlevel = 0;
2604 newValue = strchrW(command, '=');
2605 if (newValue) accessOptions |= KEY_WRITE;
2607 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2608 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2609 accessOptions, &key) != ERROR_SUCCESS) {
2610 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2611 return;
2614 /* If no parameters then list all associations */
2615 if (*command == 0x00) {
2616 int index = 0;
2618 /* Enumerate all the keys */
2619 while (rc != ERROR_NO_MORE_ITEMS) {
2620 WCHAR keyName[MAXSTRING];
2621 DWORD nameLen;
2623 /* Find the next value */
2624 nameLen = MAXSTRING;
2625 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2627 if (rc == ERROR_SUCCESS) {
2629 /* Only interested in extension ones if assoc, or others
2630 if not assoc */
2631 if ((keyName[0] == '.' && assoc) ||
2632 (!(keyName[0] == '.') && (!assoc)))
2634 WCHAR subkey[MAXSTRING];
2635 strcpyW(subkey, keyName);
2636 if (!assoc) strcatW(subkey, shOpCmdW);
2638 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2640 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2641 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2642 WCMD_output_asis(keyName);
2643 WCMD_output_asis(equalW);
2644 /* If no default value found, leave line empty after '=' */
2645 if (rc == ERROR_SUCCESS) {
2646 WCMD_output_asis(keyValue);
2648 WCMD_output_asis(newline);
2649 RegCloseKey(readKey);
2655 } else {
2657 /* Parameter supplied - if no '=' on command line, its a query */
2658 if (newValue == NULL) {
2659 WCHAR *space;
2660 WCHAR subkey[MAXSTRING];
2662 /* Query terminates the parameter at the first space */
2663 strcpyW(keyValue, command);
2664 space = strchrW(keyValue, ' ');
2665 if (space) *space=0x00;
2667 /* Set up key name */
2668 strcpyW(subkey, keyValue);
2669 if (!assoc) strcatW(subkey, shOpCmdW);
2671 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2673 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2674 WCMD_output_asis(command);
2675 WCMD_output_asis(equalW);
2676 /* If no default value found, leave line empty after '=' */
2677 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2678 WCMD_output_asis(newline);
2679 RegCloseKey(readKey);
2681 } else {
2682 WCHAR msgbuffer[MAXSTRING];
2683 WCHAR outbuffer[MAXSTRING];
2685 /* Load the translated 'File association not found' */
2686 if (assoc) {
2687 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2688 } else {
2689 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2691 wsprintfW(outbuffer, msgbuffer, keyValue);
2692 WCMD_output_asis(outbuffer);
2693 errorlevel = 2;
2696 /* Not a query - its a set or clear of a value */
2697 } else {
2699 WCHAR subkey[MAXSTRING];
2701 /* Get pointer to new value */
2702 *newValue = 0x00;
2703 newValue++;
2705 /* Set up key name */
2706 strcpyW(subkey, command);
2707 if (!assoc) strcatW(subkey, shOpCmdW);
2709 /* If nothing after '=' then clear value - only valid for ASSOC */
2710 if (*newValue == 0x00) {
2712 if (assoc) rc = RegDeleteKeyW(key, command);
2713 if (assoc && rc == ERROR_SUCCESS) {
2714 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2716 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2717 WCMD_print_error();
2718 errorlevel = 2;
2720 } else {
2721 WCHAR msgbuffer[MAXSTRING];
2722 WCHAR outbuffer[MAXSTRING];
2724 /* Load the translated 'File association not found' */
2725 if (assoc) {
2726 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2727 sizeof(msgbuffer)/sizeof(WCHAR));
2728 } else {
2729 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2730 sizeof(msgbuffer)/sizeof(WCHAR));
2732 wsprintfW(outbuffer, msgbuffer, keyValue);
2733 WCMD_output_asis(outbuffer);
2734 errorlevel = 2;
2737 /* It really is a set value = contents */
2738 } else {
2739 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2740 accessOptions, NULL, &readKey, NULL);
2741 if (rc == ERROR_SUCCESS) {
2742 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2743 (LPBYTE)newValue, strlenW(newValue));
2744 RegCloseKey(readKey);
2747 if (rc != ERROR_SUCCESS) {
2748 WCMD_print_error();
2749 errorlevel = 2;
2750 } else {
2751 WCMD_output_asis(command);
2752 WCMD_output_asis(equalW);
2753 WCMD_output_asis(newValue);
2754 WCMD_output_asis(newline);
2760 /* Clean up */
2761 RegCloseKey(key);
2764 /****************************************************************************
2765 * WCMD_color
2767 * Clear the terminal screen.
2770 void WCMD_color (void) {
2772 /* Emulate by filling the screen from the top left to bottom right with
2773 spaces, then moving the cursor to the top left afterwards */
2774 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2775 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2777 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2778 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2779 return;
2782 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2784 COORD topLeft;
2785 DWORD screenSize;
2786 DWORD color = 0;
2788 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2790 topLeft.X = 0;
2791 topLeft.Y = 0;
2793 /* Convert the color hex digits */
2794 if (param1[0] == 0x00) {
2795 color = defaultColor;
2796 } else {
2797 color = strtoulW(param1, NULL, 16);
2800 /* Fail if fg == bg color */
2801 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2802 errorlevel = 1;
2803 return;
2806 /* Set the current screen contents and ensure all future writes
2807 remain this color */
2808 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2809 SetConsoleTextAttribute(hStdOut, color);