cmd.exe: for loop by default only matches files.
[wine.git] / programs / cmd / builtins.c
blob2a075c53fdb6f6cf82fc1e5d71aef4a06f61400c
1 /*
2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 * NOTES:
23 * On entry to each function, global variables quals, param1, param2 contain
24 * the qualifiers (uppercased and concatenated) and parameters entered, with
25 * environment-variable and batch parameter substitution already done.
29 * FIXME:
30 * - No support for pipes, shell parameters
31 * - Lots of functionality missing from builtins
32 * - Messages etc need international support
35 #define WIN32_LEAN_AND_MEAN
37 #include "wcmd.h"
38 #include <shellapi.h>
39 #include "wine/debug.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
43 static void WCMD_part_execute(CMD_LIST **commands, WCHAR *firstcmd, WCHAR *variable,
44 WCHAR *value, BOOL isIF, BOOL conditionTRUE);
46 struct env_stack *saved_environment;
47 struct env_stack *pushd_directories;
49 extern HINSTANCE hinst;
50 extern WCHAR inbuilt[][10];
51 extern int echo_mode, verify_mode, defaultColor;
52 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
53 extern BATCH_CONTEXT *context;
54 extern DWORD errorlevel;
56 static const WCHAR dotW[] = {'.','\0'};
57 static const WCHAR dotdotW[] = {'.','.','\0'};
58 static const WCHAR slashW[] = {'\\','\0'};
59 static const WCHAR starW[] = {'*','\0'};
60 static const WCHAR equalW[] = {'=','\0'};
61 static const WCHAR fslashW[] = {'/','\0'};
62 static const WCHAR onW[] = {'O','N','\0'};
63 static const WCHAR offW[] = {'O','F','F','\0'};
64 static const WCHAR parmY[] = {'/','Y','\0'};
65 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
66 static const WCHAR nullW[] = {'\0'};
68 /****************************************************************************
69 * WCMD_clear_screen
71 * Clear the terminal screen.
74 void WCMD_clear_screen (void) {
76 /* Emulate by filling the screen from the top left to bottom right with
77 spaces, then moving the cursor to the top left afterwards */
78 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
79 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
81 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
83 COORD topLeft;
84 DWORD screenSize;
86 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
88 topLeft.X = 0;
89 topLeft.Y = 0;
90 FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
91 SetConsoleCursorPosition(hStdOut, topLeft);
95 /****************************************************************************
96 * WCMD_change_tty
98 * Change the default i/o device (ie redirect STDin/STDout).
101 void WCMD_change_tty (void) {
103 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
107 /****************************************************************************
108 * WCMD_copy
110 * Copy a file or wildcarded set.
111 * FIXME: No wildcard support
114 void WCMD_copy (void) {
116 WIN32_FIND_DATA fd;
117 HANDLE hff;
118 BOOL force, status;
119 WCHAR outpath[MAX_PATH], inpath[MAX_PATH], *infile, copycmd[3];
120 DWORD len;
121 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
123 if (param1[0] == 0x00) {
124 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
125 return;
128 if ((strchrW(param1,'*') != NULL) && (strchrW(param1,'%') != NULL)) {
129 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
130 return;
133 /* If no destination supplied, assume current directory */
134 if (param2[0] == 0x00) {
135 strcpyW(param2, dotW);
138 GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
139 if (outpath[strlenW(outpath) - 1] == '\\')
140 outpath[strlenW(outpath) - 1] = '\0';
141 hff = FindFirstFile (outpath, &fd);
142 if (hff != INVALID_HANDLE_VALUE) {
143 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
144 GetFullPathName (param1, sizeof(inpath)/sizeof(WCHAR), inpath, &infile);
145 strcatW (outpath, slashW);
146 strcatW (outpath, infile);
148 FindClose (hff);
151 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
152 if (strstrW (quals, parmNoY))
153 force = FALSE;
154 else if (strstrW (quals, parmY))
155 force = TRUE;
156 else {
157 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
158 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
161 if (!force) {
162 hff = FindFirstFile (outpath, &fd);
163 if (hff != INVALID_HANDLE_VALUE) {
164 WCHAR buffer[MAXSTRING];
166 FindClose (hff);
168 wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outpath);
169 force = WCMD_ask_confirm(buffer, FALSE, NULL);
171 else force = TRUE;
173 if (force) {
174 status = CopyFile (param1, outpath, FALSE);
175 if (!status) WCMD_print_error ();
179 /****************************************************************************
180 * WCMD_create_dir
182 * Create a directory.
184 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
185 * they do not already exist.
188 static BOOL create_full_path(WCHAR* path)
190 int len;
191 WCHAR *new_path;
192 BOOL ret = TRUE;
194 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
195 strcpyW(new_path,path);
197 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
198 new_path[len - 1] = 0;
200 while (!CreateDirectory(new_path,NULL))
202 WCHAR *slash;
203 DWORD last_error = GetLastError();
204 if (last_error == ERROR_ALREADY_EXISTS)
205 break;
207 if (last_error != ERROR_PATH_NOT_FOUND)
209 ret = FALSE;
210 break;
213 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
215 ret = FALSE;
216 break;
219 len = slash - new_path;
220 new_path[len] = 0;
221 if (!create_full_path(new_path))
223 ret = FALSE;
224 break;
226 new_path[len] = '\\';
228 HeapFree(GetProcessHeap(),0,new_path);
229 return ret;
232 void WCMD_create_dir (void) {
234 if (param1[0] == 0x00) {
235 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
236 return;
238 if (!create_full_path(param1)) WCMD_print_error ();
241 /****************************************************************************
242 * WCMD_delete
244 * Delete a file or wildcarded set.
246 * Note on /A:
247 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
248 * - Each set is a pattern, eg /ahr /as-r means
249 * readonly+hidden OR nonreadonly system files
250 * - The '-' applies to a single field, ie /a:-hr means read only
251 * non-hidden files
254 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
256 int argno = 0;
257 int argsProcessed = 0;
258 WCHAR *argN = command;
259 BOOL foundAny = FALSE;
260 static const WCHAR parmA[] = {'/','A','\0'};
261 static const WCHAR parmQ[] = {'/','Q','\0'};
262 static const WCHAR parmP[] = {'/','P','\0'};
263 static const WCHAR parmS[] = {'/','S','\0'};
264 static const WCHAR parmF[] = {'/','F','\0'};
266 /* If not recursing, clear error flag */
267 if (expectDir) errorlevel = 0;
269 /* Loop through all args */
270 while (argN) {
271 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
272 WCHAR argCopy[MAX_PATH];
274 if (argN && argN[0] != '/') {
276 WIN32_FIND_DATA fd;
277 HANDLE hff;
278 WCHAR fpath[MAX_PATH];
279 WCHAR *p;
280 BOOL handleParm = TRUE;
281 BOOL found = FALSE;
282 static const WCHAR anyExt[]= {'.','*','\0'};
284 strcpyW(argCopy, thisArg);
285 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
286 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
287 argsProcessed++;
289 /* If filename part of parameter is * or *.*, prompt unless
290 /Q supplied. */
291 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
293 WCHAR drive[10];
294 WCHAR dir[MAX_PATH];
295 WCHAR fname[MAX_PATH];
296 WCHAR ext[MAX_PATH];
298 /* Convert path into actual directory spec */
299 GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
300 WCMD_splitpath(fpath, drive, dir, fname, ext);
302 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
303 if ((strcmpW(fname, starW) == 0) &&
304 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
305 BOOL ok;
306 WCHAR question[MAXSTRING];
307 static const WCHAR fmt[] = {'%','s',' ','\0'};
309 /* Note: Flag as found, to avoid file not found message */
310 found = TRUE;
312 /* Ask for confirmation */
313 wsprintf(question, fmt, fpath);
314 ok = WCMD_ask_confirm(question, TRUE, NULL);
316 /* Abort if answer is 'N' */
317 if (!ok) continue;
321 /* First, try to delete in the current directory */
322 hff = FindFirstFile (argCopy, &fd);
323 if (hff == INVALID_HANDLE_VALUE) {
324 handleParm = FALSE;
325 } else {
326 found = TRUE;
329 /* Support del <dirname> by just deleting all files dirname\* */
330 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
331 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
332 WCHAR modifiedParm[MAX_PATH];
333 static const WCHAR slashStar[] = {'\\','*','\0'};
335 strcpyW(modifiedParm, argCopy);
336 strcatW(modifiedParm, slashStar);
337 FindClose(hff);
338 found = TRUE;
339 WCMD_delete(modifiedParm, FALSE);
341 } else if (handleParm) {
343 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
344 strcpyW (fpath, argCopy);
345 do {
346 p = strrchrW (fpath, '\\');
347 if (p != NULL) {
348 *++p = '\0';
349 strcatW (fpath, fd.cFileName);
351 else strcpyW (fpath, fd.cFileName);
352 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
353 BOOL ok = TRUE;
354 WCHAR *nextA = strstrW (quals, parmA);
356 /* Handle attribute matching (/A) */
357 if (nextA != NULL) {
358 ok = FALSE;
359 while (nextA != NULL && !ok) {
361 WCHAR *thisA = (nextA+2);
362 BOOL stillOK = TRUE;
364 /* Skip optional : */
365 if (*thisA == ':') thisA++;
367 /* Parse each of the /A[:]xxx in turn */
368 while (*thisA && *thisA != '/') {
369 BOOL negate = FALSE;
370 BOOL attribute = FALSE;
372 /* Match negation of attribute first */
373 if (*thisA == '-') {
374 negate=TRUE;
375 thisA++;
378 /* Match attribute */
379 switch (*thisA) {
380 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
381 break;
382 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
383 break;
384 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
385 break;
386 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
387 break;
388 default:
389 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
392 /* Now check result, keeping a running boolean about whether it
393 matches all parsed attribues so far */
394 if (attribute && !negate) {
395 stillOK = stillOK;
396 } else if (!attribute && negate) {
397 stillOK = stillOK;
398 } else {
399 stillOK = FALSE;
401 thisA++;
404 /* Save the running total as the final result */
405 ok = stillOK;
407 /* Step on to next /A set */
408 nextA = strstrW (nextA+1, parmA);
412 /* /P means prompt for each file */
413 if (ok && strstrW (quals, parmP) != NULL) {
414 WCHAR question[MAXSTRING];
416 /* Ask for confirmation */
417 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
418 ok = WCMD_ask_confirm(question, FALSE, NULL);
421 /* Only proceed if ok to */
422 if (ok) {
424 /* If file is read only, and /F supplied, delete it */
425 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
426 strstrW (quals, parmF) != NULL) {
427 SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
430 /* Now do the delete */
431 if (!DeleteFile (fpath)) WCMD_print_error ();
435 } while (FindNextFile(hff, &fd) != 0);
436 FindClose (hff);
439 /* Now recurse into all subdirectories handling the parameter in the same way */
440 if (strstrW (quals, parmS) != NULL) {
442 WCHAR thisDir[MAX_PATH];
443 int cPos;
445 WCHAR drive[10];
446 WCHAR dir[MAX_PATH];
447 WCHAR fname[MAX_PATH];
448 WCHAR ext[MAX_PATH];
450 /* Convert path into actual directory spec */
451 GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
452 WCMD_splitpath(thisDir, drive, dir, fname, ext);
454 strcpyW(thisDir, drive);
455 strcatW(thisDir, dir);
456 cPos = strlenW(thisDir);
458 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
460 /* Append '*' to the directory */
461 thisDir[cPos] = '*';
462 thisDir[cPos+1] = 0x00;
464 hff = FindFirstFile (thisDir, &fd);
466 /* Remove residual '*' */
467 thisDir[cPos] = 0x00;
469 if (hff != INVALID_HANDLE_VALUE) {
470 DIRECTORY_STACK *allDirs = NULL;
471 DIRECTORY_STACK *lastEntry = NULL;
473 do {
474 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
475 (strcmpW(fd.cFileName, dotdotW) != 0) &&
476 (strcmpW(fd.cFileName, dotW) != 0)) {
478 DIRECTORY_STACK *nextDir;
479 WCHAR subParm[MAX_PATH];
481 /* Work out search parameter in sub dir */
482 strcpyW (subParm, thisDir);
483 strcatW (subParm, fd.cFileName);
484 strcatW (subParm, slashW);
485 strcatW (subParm, fname);
486 strcatW (subParm, ext);
487 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
489 /* Allocate memory, add to list */
490 nextDir = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
491 if (allDirs == NULL) allDirs = nextDir;
492 if (lastEntry != NULL) lastEntry->next = nextDir;
493 lastEntry = nextDir;
494 nextDir->next = NULL;
495 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
496 (strlenW(subParm)+1) * sizeof(WCHAR));
497 strcpyW(nextDir->dirName, subParm);
499 } while (FindNextFile(hff, &fd) != 0);
500 FindClose (hff);
502 /* Go through each subdir doing the delete */
503 while (allDirs != NULL) {
504 DIRECTORY_STACK *tempDir;
506 tempDir = allDirs->next;
507 found |= WCMD_delete (allDirs->dirName, FALSE);
509 HeapFree(GetProcessHeap(),0,allDirs->dirName);
510 HeapFree(GetProcessHeap(),0,allDirs);
511 allDirs = tempDir;
515 /* Keep running total to see if any found, and if not recursing
516 issue error message */
517 if (expectDir) {
518 if (!found) {
519 errorlevel = 1;
520 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
523 foundAny |= found;
527 /* Handle no valid args */
528 if (argsProcessed == 0) {
529 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
532 return foundAny;
535 /****************************************************************************
536 * WCMD_echo
538 * Echo input to the screen (or not). We don't try to emulate the bugs
539 * in DOS (try typing "ECHO ON AGAIN" for an example).
542 void WCMD_echo (const WCHAR *command) {
544 int count;
546 if ((command[0] == '.') && (command[1] == 0)) {
547 WCMD_output (newline);
548 return;
550 if (command[0]==' ')
551 command++;
552 count = strlenW(command);
553 if (count == 0) {
554 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
555 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
556 return;
558 if (lstrcmpiW(command, onW) == 0) {
559 echo_mode = 1;
560 return;
562 if (lstrcmpiW(command, offW) == 0) {
563 echo_mode = 0;
564 return;
566 WCMD_output_asis (command);
567 WCMD_output (newline);
571 /**************************************************************************
572 * WCMD_for
574 * Batch file loop processing.
576 * On entry: cmdList contains the syntax up to the set
577 * next cmdList and all in that bracket contain the set data
578 * next cmdlist contains the DO cmd
579 * following that is either brackets or && entries (as per if)
581 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
582 * will probably work here, but the reverse is not necessarily the case...
585 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
587 WIN32_FIND_DATA fd;
588 HANDLE hff;
589 int i;
590 const WCHAR inW[] = {'i', 'n', '\0'};
591 const WCHAR doW[] = {'d', 'o', ' ','\0'};
592 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
593 WCHAR variable[4];
594 WCHAR *firstCmd;
595 int thisDepth;
596 BOOL isDirs = FALSE;
598 /* Check:
599 the first line includes the % variable name as first parm
600 we have been provided with more parts to the command
601 and there is at least some set data
602 and IN as the one after that */
603 if (lstrcmpiW (WCMD_parameter (p, 1, NULL), inW)
604 || (*cmdList) == NULL
605 || (*cmdList)->nextcommand == NULL
606 || (param1[0] != '%')
607 || (strlenW(param1) > 3)) {
608 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
609 return;
612 /* Save away where the set of data starts and the variable */
613 strcpyW(variable, param1);
614 thisDepth = (*cmdList)->bracketDepth;
615 *cmdList = (*cmdList)->nextcommand;
616 setStart = (*cmdList);
618 /* Skip until the close bracket */
619 WINE_TRACE("Searching %p as the set\n", *cmdList);
620 while (*cmdList &&
621 (*cmdList)->command != NULL &&
622 (*cmdList)->bracketDepth > thisDepth) {
623 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
624 *cmdList = (*cmdList)->nextcommand;
627 /* Skip the close bracket, if there is one */
628 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
630 /* Syntax error if missing close bracket, or nothing following it
631 and once we have the complete set, we expect a DO */
632 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
633 if ((*cmdList == NULL) ||
634 (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
635 (*cmdList)->command, 3, doW, -1) != 2)) {
636 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
637 return;
640 /* Save away the starting position for the commands (and offset for the
641 first one */
642 cmdStart = *cmdList;
643 cmdEnd = *cmdList;
644 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
646 thisSet = setStart;
647 /* Loop through all set entries */
648 while (thisSet &&
649 thisSet->command != NULL &&
650 thisSet->bracketDepth >= thisDepth) {
652 /* Loop through all entries on the same line */
653 WCHAR *item;
655 WINE_TRACE("Processing for set %p\n", thisSet);
656 i = 0;
657 while (*(item = WCMD_parameter (thisSet->command, i, NULL))) {
660 * If the parameter within the set has a wildcard then search for matching files
661 * otherwise do a literal substitution.
663 static const WCHAR wildcards[] = {'*','?','\0'};
664 CMD_LIST *thisCmdStart = cmdStart;
666 WINE_TRACE("Processing for item '%s'\n", wine_dbgstr_w(item));
667 if (strpbrkW (item, wildcards)) {
668 hff = FindFirstFile (item, &fd);
669 if (hff != INVALID_HANDLE_VALUE) {
670 do {
671 BOOL isDirectory = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
672 if ((isDirs && isDirectory) ||
673 (!isDirs && !isDirectory))
675 thisCmdStart = cmdStart;
676 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
677 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
678 fd.cFileName, FALSE, TRUE);
681 } while (FindNextFile(hff, &fd) != 0);
682 FindClose (hff);
684 } else {
685 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
688 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
689 cmdEnd = thisCmdStart;
690 i++;
693 /* Move onto the next set line */
694 thisSet = thisSet->nextcommand;
697 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
698 all processing, OR it should be pointing to the end of && processing OR
699 it should be pointing at the NULL end of bracket for the DO. The return
700 value needs to be the NEXT command to execute, which it either is, or
701 we need to step over the closing bracket */
702 *cmdList = cmdEnd;
703 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
707 /*****************************************************************************
708 * WCMD_part_execute
710 * Execute a command, and any && or bracketed follow on to the command. The
711 * first command to be executed may not be at the front of the
712 * commands->thiscommand string (eg. it may point after a DO or ELSE
713 * Returns TRUE if something like exit or goto has aborted all processing
715 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
716 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
718 CMD_LIST *curPosition = *cmdList;
719 int myDepth = (*cmdList)->bracketDepth;
721 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
722 cmdList, wine_dbgstr_w(firstcmd),
723 wine_dbgstr_w(variable), wine_dbgstr_w(value),
724 conditionTRUE);
726 /* Skip leading whitespace between condition and the command */
727 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
729 /* Process the first command, if there is one */
730 if (conditionTRUE && firstcmd && *firstcmd) {
731 WCHAR *command = WCMD_strdupW(firstcmd);
732 WCMD_execute (firstcmd, variable, value, cmdList);
733 free (command);
737 /* If it didnt move the position, step to next command */
738 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
740 /* Process any other parts of the command */
741 if (*cmdList) {
742 BOOL processThese = TRUE;
744 if (isIF) processThese = conditionTRUE;
746 while (*cmdList) {
747 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
749 /* execute all appropriate commands */
750 curPosition = *cmdList;
752 WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
753 *cmdList,
754 (*cmdList)->isAmphersand,
755 (*cmdList)->bracketDepth, myDepth);
757 /* Execute any appended to the statement with &&'s */
758 if ((*cmdList)->isAmphersand) {
759 if (processThese) {
760 WCMD_execute ((*cmdList)->command, variable, value, cmdList);
762 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
764 /* Execute any appended to the statement with (...) */
765 } else if ((*cmdList)->bracketDepth > myDepth) {
766 if (processThese) {
767 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
768 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
770 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
772 /* End of the command - does 'ELSE ' follow as the next command? */
773 } else {
774 if (isIF && CompareString (LOCALE_USER_DEFAULT,
775 NORM_IGNORECASE | SORT_STRINGSORT,
776 (*cmdList)->command, 5, ifElse, -1) == 2) {
778 /* Swap between if and else processing */
779 processThese = !processThese;
781 /* Process the ELSE part */
782 if (processThese) {
783 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
785 /* Skip leading whitespace between condition and the command */
786 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
787 if (*cmd) {
788 WCMD_execute (cmd, variable, value, cmdList);
791 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
792 } else {
793 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
794 break;
799 return;
802 /*****************************************************************************
803 * WCMD_Execute
805 * Execute a command after substituting variable text for the supplied parameter
808 void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) {
810 WCHAR *new_cmd, *p, *s, *dup;
811 int size;
813 if (param) {
814 size = (strlenW (orig_cmd) + 1) * sizeof(WCHAR);
815 new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
816 dup = s = WCMD_strdupW(orig_cmd);
818 while ((p = strstrW (s, param))) {
819 *p = '\0';
820 size += strlenW (subst) * sizeof(WCHAR);
821 new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
822 strcatW (new_cmd, s);
823 strcatW (new_cmd, subst);
824 s = p + strlenW (param);
826 strcatW (new_cmd, s);
827 WCMD_process_command (new_cmd, cmdList);
828 free (dup);
829 LocalFree ((HANDLE)new_cmd);
830 } else {
831 WCMD_process_command (orig_cmd, cmdList);
836 /**************************************************************************
837 * WCMD_give_help
839 * Simple on-line help. Help text is stored in the resource file.
842 void WCMD_give_help (WCHAR *command) {
844 int i;
846 command = WCMD_strtrim_leading_spaces(command);
847 if (strlenW(command) == 0) {
848 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
850 else {
851 for (i=0; i<=WCMD_EXIT; i++) {
852 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
853 param1, -1, inbuilt[i], -1) == 2) {
854 WCMD_output_asis (WCMD_LoadMessage(i));
855 return;
858 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
860 return;
863 /****************************************************************************
864 * WCMD_go_to
866 * Batch file jump instruction. Not the most efficient algorithm ;-)
867 * Prints error message if the specified label cannot be found - the file pointer is
868 * then at EOF, effectively stopping the batch file.
869 * FIXME: DOS is supposed to allow labels with spaces - we don't.
872 void WCMD_goto (CMD_LIST **cmdList) {
874 WCHAR string[MAX_PATH];
876 /* Do not process any more parts of a processed multipart or multilines command */
877 *cmdList = NULL;
879 if (param1[0] == 0x00) {
880 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
881 return;
883 if (context != NULL) {
884 WCHAR *paramStart = param1;
885 static const WCHAR eofW[] = {':','e','o','f','\0'};
887 /* Handle special :EOF label */
888 if (lstrcmpiW (eofW, param1) == 0) {
889 context -> skip_rest = TRUE;
890 return;
893 /* Support goto :label as well as goto label */
894 if (*paramStart == ':') paramStart++;
896 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
897 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
898 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
900 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
902 return;
905 /*****************************************************************************
906 * WCMD_pushd
908 * Push a directory onto the stack
911 void WCMD_pushd (WCHAR *command) {
912 struct env_stack *curdir;
913 WCHAR *thisdir;
914 static const WCHAR parmD[] = {'/','D','\0'};
916 if (strchrW(command, '/') != NULL) {
917 SetLastError(ERROR_INVALID_PARAMETER);
918 WCMD_print_error();
919 return;
922 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
923 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
924 if( !curdir || !thisdir ) {
925 LocalFree(curdir);
926 LocalFree(thisdir);
927 WINE_ERR ("out of memory\n");
928 return;
931 /* Change directory using CD code with /D parameter */
932 strcpyW(quals, parmD);
933 GetCurrentDirectoryW (1024, thisdir);
934 errorlevel = 0;
935 WCMD_setshow_default(command);
936 if (errorlevel) {
937 LocalFree(curdir);
938 LocalFree(thisdir);
939 return;
940 } else {
941 curdir -> next = pushd_directories;
942 curdir -> strings = thisdir;
943 if (pushd_directories == NULL) {
944 curdir -> u.stackdepth = 1;
945 } else {
946 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
948 pushd_directories = curdir;
953 /*****************************************************************************
954 * WCMD_popd
956 * Pop a directory from the stack
959 void WCMD_popd (void) {
960 struct env_stack *temp = pushd_directories;
962 if (!pushd_directories)
963 return;
965 /* pop the old environment from the stack, and make it the current dir */
966 pushd_directories = temp->next;
967 SetCurrentDirectoryW(temp->strings);
968 LocalFree (temp->strings);
969 LocalFree (temp);
972 /****************************************************************************
973 * WCMD_if
975 * Batch file conditional.
977 * On entry, cmdlist will point to command containing the IF, and optionally
978 * the first command to execute (if brackets not found)
979 * If &&'s were found, this may be followed by a record flagged as isAmpersand
980 * If ('s were found, execute all within that bracket
981 * Command may optionally be followed by an ELSE - need to skip instructions
982 * in the else using the same logic
984 * FIXME: Much more syntax checking needed!
987 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
989 int negate = 0, test = 0;
990 WCHAR condition[MAX_PATH], *command, *s;
991 static const WCHAR notW[] = {'n','o','t','\0'};
992 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
993 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
994 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
995 static const WCHAR eqeqW[] = {'=','=','\0'};
997 if (!lstrcmpiW (param1, notW)) {
998 negate = 1;
999 strcpyW (condition, param2);
1001 else {
1002 strcpyW (condition, param1);
1004 if (!lstrcmpiW (condition, errlvlW)) {
1005 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1006 WCMD_parameter (p, 2+negate, &command);
1008 else if (!lstrcmpiW (condition, existW)) {
1009 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1010 test = 1;
1012 WCMD_parameter (p, 2+negate, &command);
1014 else if (!lstrcmpiW (condition, defdW)) {
1015 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1016 test = 1;
1018 WCMD_parameter (p, 2+negate, &command);
1020 else if ((s = strstrW (p, eqeqW))) {
1021 s += 2;
1022 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1023 WCMD_parameter (s, 1, &command);
1025 else {
1026 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1027 return;
1030 /* Process rest of IF statement which is on the same line
1031 Note: This may process all or some of the cmdList (eg a GOTO) */
1032 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1035 /****************************************************************************
1036 * WCMD_move
1038 * Move a file, directory tree or wildcarded set of files.
1041 void WCMD_move (void) {
1043 int status;
1044 WIN32_FIND_DATA fd;
1045 HANDLE hff;
1046 WCHAR input[MAX_PATH];
1047 WCHAR output[MAX_PATH];
1048 WCHAR drive[10];
1049 WCHAR dir[MAX_PATH];
1050 WCHAR fname[MAX_PATH];
1051 WCHAR ext[MAX_PATH];
1053 if (param1[0] == 0x00) {
1054 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1055 return;
1058 /* If no destination supplied, assume current directory */
1059 if (param2[0] == 0x00) {
1060 strcpyW(param2, dotW);
1063 /* If 2nd parm is directory, then use original filename */
1064 /* Convert partial path to full path */
1065 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1066 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1067 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1068 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1070 /* Split into components */
1071 WCMD_splitpath(input, drive, dir, fname, ext);
1073 hff = FindFirstFile (input, &fd);
1074 while (hff != INVALID_HANDLE_VALUE) {
1075 WCHAR dest[MAX_PATH];
1076 WCHAR src[MAX_PATH];
1077 DWORD attribs;
1079 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1081 /* Build src & dest name */
1082 strcpyW(src, drive);
1083 strcatW(src, dir);
1085 /* See if dest is an existing directory */
1086 attribs = GetFileAttributes(output);
1087 if (attribs != INVALID_FILE_ATTRIBUTES &&
1088 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1089 strcpyW(dest, output);
1090 strcatW(dest, slashW);
1091 strcatW(dest, fd.cFileName);
1092 } else {
1093 strcpyW(dest, output);
1096 strcatW(src, fd.cFileName);
1098 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1099 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1101 /* Check if file is read only, otherwise move it */
1102 attribs = GetFileAttributes(src);
1103 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1104 (attribs & FILE_ATTRIBUTE_READONLY)) {
1105 SetLastError(ERROR_ACCESS_DENIED);
1106 status = 0;
1107 } else {
1108 BOOL ok = TRUE;
1110 /* If destination exists, prompt unless /Y supplied */
1111 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1112 BOOL force = FALSE;
1113 WCHAR copycmd[MAXSTRING];
1114 int len;
1116 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1117 if (strstrW (quals, parmNoY))
1118 force = FALSE;
1119 else if (strstrW (quals, parmY))
1120 force = TRUE;
1121 else {
1122 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1123 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1124 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1125 && ! lstrcmpiW (copycmd, parmY));
1128 /* Prompt if overwriting */
1129 if (!force) {
1130 WCHAR question[MAXSTRING];
1131 WCHAR yesChar[10];
1133 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1135 /* Ask for confirmation */
1136 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1137 ok = WCMD_ask_confirm(question, FALSE, NULL);
1139 /* So delete the destination prior to the move */
1140 if (ok) {
1141 if (!DeleteFile (dest)) {
1142 WCMD_print_error ();
1143 errorlevel = 1;
1144 ok = FALSE;
1150 if (ok) {
1151 status = MoveFile (src, dest);
1152 } else {
1153 status = 1; /* Anything other than 0 to prevent error msg below */
1157 if (!status) {
1158 WCMD_print_error ();
1159 errorlevel = 1;
1162 /* Step on to next match */
1163 if (FindNextFile(hff, &fd) == 0) {
1164 FindClose(hff);
1165 hff = INVALID_HANDLE_VALUE;
1166 break;
1171 /****************************************************************************
1172 * WCMD_pause
1174 * Wait for keyboard input.
1177 void WCMD_pause (void) {
1179 DWORD count;
1180 WCHAR string[32];
1182 WCMD_output (anykey);
1183 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1184 sizeof(string)/sizeof(WCHAR), &count, NULL);
1187 /****************************************************************************
1188 * WCMD_remove_dir
1190 * Delete a directory.
1193 void WCMD_remove_dir (WCHAR *command) {
1195 int argno = 0;
1196 int argsProcessed = 0;
1197 WCHAR *argN = command;
1198 static const WCHAR parmS[] = {'/','S','\0'};
1199 static const WCHAR parmQ[] = {'/','Q','\0'};
1201 /* Loop through all args */
1202 while (argN) {
1203 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1204 if (argN && argN[0] != '/') {
1205 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1206 wine_dbgstr_w(quals));
1207 argsProcessed++;
1209 /* If subdirectory search not supplied, just try to remove
1210 and report error if it fails (eg if it contains a file) */
1211 if (strstrW (quals, parmS) == NULL) {
1212 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1214 /* Otherwise use ShFileOp to recursively remove a directory */
1215 } else {
1217 SHFILEOPSTRUCT lpDir;
1219 /* Ask first */
1220 if (strstrW (quals, parmQ) == NULL) {
1221 BOOL ok;
1222 WCHAR question[MAXSTRING];
1223 static const WCHAR fmt[] = {'%','s',' ','\0'};
1225 /* Ask for confirmation */
1226 wsprintf(question, fmt, thisArg);
1227 ok = WCMD_ask_confirm(question, TRUE, NULL);
1229 /* Abort if answer is 'N' */
1230 if (!ok) return;
1233 /* Do the delete */
1234 lpDir.hwnd = NULL;
1235 lpDir.pTo = NULL;
1236 lpDir.pFrom = thisArg;
1237 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1238 lpDir.wFunc = FO_DELETE;
1239 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1244 /* Handle no valid args */
1245 if (argsProcessed == 0) {
1246 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1247 return;
1252 /****************************************************************************
1253 * WCMD_rename
1255 * Rename a file.
1258 void WCMD_rename (void) {
1260 int status;
1261 HANDLE hff;
1262 WIN32_FIND_DATA fd;
1263 WCHAR input[MAX_PATH];
1264 WCHAR *dotDst = NULL;
1265 WCHAR drive[10];
1266 WCHAR dir[MAX_PATH];
1267 WCHAR fname[MAX_PATH];
1268 WCHAR ext[MAX_PATH];
1269 DWORD attribs;
1271 errorlevel = 0;
1273 /* Must be at least two args */
1274 if (param1[0] == 0x00 || param2[0] == 0x00) {
1275 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1276 errorlevel = 1;
1277 return;
1280 /* Destination cannot contain a drive letter or directory separator */
1281 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1282 SetLastError(ERROR_INVALID_PARAMETER);
1283 WCMD_print_error();
1284 errorlevel = 1;
1285 return;
1288 /* Convert partial path to full path */
1289 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1290 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1291 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1292 dotDst = strchrW(param2, '.');
1294 /* Split into components */
1295 WCMD_splitpath(input, drive, dir, fname, ext);
1297 hff = FindFirstFile (input, &fd);
1298 while (hff != INVALID_HANDLE_VALUE) {
1299 WCHAR dest[MAX_PATH];
1300 WCHAR src[MAX_PATH];
1301 WCHAR *dotSrc = NULL;
1302 int dirLen;
1304 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1306 /* FIXME: If dest name or extension is *, replace with filename/ext
1307 part otherwise use supplied name. This supports:
1308 ren *.fred *.jim
1309 ren jim.* fred.* etc
1310 However, windows has a more complex algorithum supporting eg
1311 ?'s and *'s mid name */
1312 dotSrc = strchrW(fd.cFileName, '.');
1314 /* Build src & dest name */
1315 strcpyW(src, drive);
1316 strcatW(src, dir);
1317 strcpyW(dest, src);
1318 dirLen = strlenW(src);
1319 strcatW(src, fd.cFileName);
1321 /* Build name */
1322 if (param2[0] == '*') {
1323 strcatW(dest, fd.cFileName);
1324 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1325 } else {
1326 strcatW(dest, param2);
1327 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1330 /* Build Extension */
1331 if (dotDst && (*(dotDst+1)=='*')) {
1332 if (dotSrc) strcatW(dest, dotSrc);
1333 } else if (dotDst) {
1334 if (dotDst) strcatW(dest, dotDst);
1337 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1338 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1340 /* Check if file is read only, otherwise move it */
1341 attribs = GetFileAttributes(src);
1342 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1343 (attribs & FILE_ATTRIBUTE_READONLY)) {
1344 SetLastError(ERROR_ACCESS_DENIED);
1345 status = 0;
1346 } else {
1347 status = MoveFile (src, dest);
1350 if (!status) {
1351 WCMD_print_error ();
1352 errorlevel = 1;
1355 /* Step on to next match */
1356 if (FindNextFile(hff, &fd) == 0) {
1357 FindClose(hff);
1358 hff = INVALID_HANDLE_VALUE;
1359 break;
1364 /*****************************************************************************
1365 * WCMD_dupenv
1367 * Make a copy of the environment.
1369 static WCHAR *WCMD_dupenv( const WCHAR *env )
1371 WCHAR *env_copy;
1372 int len;
1374 if( !env )
1375 return NULL;
1377 len = 0;
1378 while ( env[len] )
1379 len += (strlenW(&env[len]) + 1);
1381 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1382 if (!env_copy)
1384 WINE_ERR("out of memory\n");
1385 return env_copy;
1387 memcpy (env_copy, env, len*sizeof (WCHAR));
1388 env_copy[len] = 0;
1390 return env_copy;
1393 /*****************************************************************************
1394 * WCMD_setlocal
1396 * setlocal pushes the environment onto a stack
1397 * Save the environment as unicode so we don't screw anything up.
1399 void WCMD_setlocal (const WCHAR *s) {
1400 WCHAR *env;
1401 struct env_stack *env_copy;
1402 WCHAR cwd[MAX_PATH];
1404 /* DISABLEEXTENSIONS ignored */
1406 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1407 if( !env_copy )
1409 WINE_ERR ("out of memory\n");
1410 return;
1413 env = GetEnvironmentStringsW ();
1415 env_copy->strings = WCMD_dupenv (env);
1416 if (env_copy->strings)
1418 env_copy->next = saved_environment;
1419 saved_environment = env_copy;
1421 /* Save the current drive letter */
1422 GetCurrentDirectory (MAX_PATH, cwd);
1423 env_copy->u.cwd = cwd[0];
1425 else
1426 LocalFree (env_copy);
1428 FreeEnvironmentStringsW (env);
1432 /*****************************************************************************
1433 * WCMD_endlocal
1435 * endlocal pops the environment off a stack
1436 * Note: When searching for '=', search from WCHAR position 1, to handle
1437 * special internal environment variables =C:, =D: etc
1439 void WCMD_endlocal (void) {
1440 WCHAR *env, *old, *p;
1441 struct env_stack *temp;
1442 int len, n;
1444 if (!saved_environment)
1445 return;
1447 /* pop the old environment from the stack */
1448 temp = saved_environment;
1449 saved_environment = temp->next;
1451 /* delete the current environment, totally */
1452 env = GetEnvironmentStringsW ();
1453 old = WCMD_dupenv (GetEnvironmentStringsW ());
1454 len = 0;
1455 while (old[len]) {
1456 n = strlenW(&old[len]) + 1;
1457 p = strchrW(&old[len] + 1, '=');
1458 if (p)
1460 *p++ = 0;
1461 SetEnvironmentVariableW (&old[len], NULL);
1463 len += n;
1465 LocalFree (old);
1466 FreeEnvironmentStringsW (env);
1468 /* restore old environment */
1469 env = temp->strings;
1470 len = 0;
1471 while (env[len]) {
1472 n = strlenW(&env[len]) + 1;
1473 p = strchrW(&env[len] + 1, '=');
1474 if (p)
1476 *p++ = 0;
1477 SetEnvironmentVariableW (&env[len], p);
1479 len += n;
1482 /* Restore current drive letter */
1483 if (IsCharAlpha(temp->u.cwd)) {
1484 WCHAR envvar[4];
1485 WCHAR cwd[MAX_PATH];
1486 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1488 wsprintf(envvar, fmt, temp->u.cwd);
1489 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1490 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1491 SetCurrentDirectory(cwd);
1495 LocalFree (env);
1496 LocalFree (temp);
1499 /*****************************************************************************
1500 * WCMD_setshow_attrib
1502 * Display and optionally sets DOS attributes on a file or directory
1504 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1505 * As a result only the Readonly flag is correctly reported, the Archive bit
1506 * is always set and the rest are not implemented. We do the Right Thing anyway.
1508 * FIXME: No SET functionality.
1512 void WCMD_setshow_attrib (void) {
1514 DWORD count;
1515 HANDLE hff;
1516 WIN32_FIND_DATA fd;
1517 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1519 if (param1[0] == '-') {
1520 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1521 return;
1524 if (strlenW(param1) == 0) {
1525 static const WCHAR slashStarW[] = {'\\','*','\0'};
1527 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1528 strcatW (param1, slashStarW);
1531 hff = FindFirstFile (param1, &fd);
1532 if (hff == INVALID_HANDLE_VALUE) {
1533 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1535 else {
1536 do {
1537 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1538 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1539 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1540 flags[0] = 'H';
1542 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1543 flags[1] = 'S';
1545 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1546 flags[2] = 'A';
1548 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1549 flags[3] = 'R';
1551 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1552 flags[4] = 'T';
1554 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1555 flags[5] = 'C';
1557 WCMD_output (fmt, flags, fd.cFileName);
1558 for (count=0; count < 8; count++) flags[count] = ' ';
1560 } while (FindNextFile(hff, &fd) != 0);
1562 FindClose (hff);
1565 /*****************************************************************************
1566 * WCMD_setshow_default
1568 * Set/Show the current default directory
1571 void WCMD_setshow_default (WCHAR *command) {
1573 BOOL status;
1574 WCHAR string[1024];
1575 WCHAR cwd[1024];
1576 WCHAR *pos;
1577 WIN32_FIND_DATA fd;
1578 HANDLE hff;
1579 static const WCHAR parmD[] = {'/','D','\0'};
1581 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1583 /* Skip /D and trailing whitespace if on the front of the command line */
1584 if (CompareString (LOCALE_USER_DEFAULT,
1585 NORM_IGNORECASE | SORT_STRINGSORT,
1586 command, 2, parmD, -1) == 2) {
1587 command += 2;
1588 while (*command && *command==' ') command++;
1591 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1592 if (strlenW(command) == 0) {
1593 strcatW (cwd, newline);
1594 WCMD_output (cwd);
1596 else {
1597 /* Remove any double quotes, which may be in the
1598 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1599 pos = string;
1600 while (*command) {
1601 if (*command != '"') *pos++ = *command;
1602 command++;
1604 *pos = 0x00;
1606 /* Search for approprate directory */
1607 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1608 hff = FindFirstFile (string, &fd);
1609 while (hff != INVALID_HANDLE_VALUE) {
1610 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1611 WCHAR fpath[MAX_PATH];
1612 WCHAR drive[10];
1613 WCHAR dir[MAX_PATH];
1614 WCHAR fname[MAX_PATH];
1615 WCHAR ext[MAX_PATH];
1616 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1618 /* Convert path into actual directory spec */
1619 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1620 WCMD_splitpath(fpath, drive, dir, fname, ext);
1622 /* Rebuild path */
1623 wsprintf(string, fmt, drive, dir, fd.cFileName);
1625 FindClose(hff);
1626 hff = INVALID_HANDLE_VALUE;
1627 break;
1630 /* Step on to next match */
1631 if (FindNextFile(hff, &fd) == 0) {
1632 FindClose(hff);
1633 hff = INVALID_HANDLE_VALUE;
1634 break;
1638 /* Change to that directory */
1639 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1641 status = SetCurrentDirectory (string);
1642 if (!status) {
1643 errorlevel = 1;
1644 WCMD_print_error ();
1645 return;
1646 } else {
1648 /* Restore old directory if drive letter would change, and
1649 CD x:\directory /D (or pushd c:\directory) not supplied */
1650 if ((strstrW(quals, parmD) == NULL) &&
1651 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1652 SetCurrentDirectory(cwd);
1656 /* Set special =C: type environment variable, for drive letter of
1657 change of directory, even if path was restored due to missing
1658 /D (allows changing drive letter when not resident on that
1659 drive */
1660 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1661 WCHAR env[4];
1662 strcpyW(env, equalW);
1663 memcpy(env+1, string, 2 * sizeof(WCHAR));
1664 env[3] = 0x00;
1665 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1666 SetEnvironmentVariable(env, string);
1670 return;
1673 /****************************************************************************
1674 * WCMD_setshow_date
1676 * Set/Show the system date
1677 * FIXME: Can't change date yet
1680 void WCMD_setshow_date (void) {
1682 WCHAR curdate[64], buffer[64];
1683 DWORD count;
1684 static const WCHAR parmT[] = {'/','T','\0'};
1686 if (strlenW(param1) == 0) {
1687 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1688 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1689 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1690 if (strstrW (quals, parmT) == NULL) {
1691 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1692 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1693 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1694 if (count > 2) {
1695 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1699 else WCMD_print_error ();
1701 else {
1702 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1706 /****************************************************************************
1707 * WCMD_compare
1709 static int WCMD_compare( const void *a, const void *b )
1711 int r;
1712 const WCHAR * const *str_a = a, * const *str_b = b;
1713 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1714 *str_a, -1, *str_b, -1 );
1715 if( r == CSTR_LESS_THAN ) return -1;
1716 if( r == CSTR_GREATER_THAN ) return 1;
1717 return 0;
1720 /****************************************************************************
1721 * WCMD_setshow_sortenv
1723 * sort variables into order for display
1724 * Optionally only display those who start with a stub
1725 * returns the count displayed
1727 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1729 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1730 const WCHAR **str;
1732 if (stub) stublen = strlenW(stub);
1734 /* count the number of strings, and the total length */
1735 while ( s[len] ) {
1736 len += (strlenW(&s[len]) + 1);
1737 count++;
1740 /* add the strings to an array */
1741 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1742 if( !str )
1743 return 0;
1744 str[0] = s;
1745 for( i=1; i<count; i++ )
1746 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1748 /* sort the array */
1749 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1751 /* print it */
1752 for( i=0; i<count; i++ ) {
1753 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1754 NORM_IGNORECASE | SORT_STRINGSORT,
1755 str[i], stublen, stub, -1) == 2) {
1756 /* Don't display special internal variables */
1757 if (str[i][0] != '=') {
1758 WCMD_output_asis(str[i]);
1759 WCMD_output_asis(newline);
1760 displayedcount++;
1765 LocalFree( str );
1766 return displayedcount;
1769 /****************************************************************************
1770 * WCMD_setshow_env
1772 * Set/Show the environment variables
1775 void WCMD_setshow_env (WCHAR *s) {
1777 LPVOID env;
1778 WCHAR *p;
1779 int status;
1780 static const WCHAR parmP[] = {'/','P','\0'};
1782 errorlevel = 0;
1783 if (param1[0] == 0x00 && quals[0] == 0x00) {
1784 env = GetEnvironmentStrings ();
1785 WCMD_setshow_sortenv( env, NULL );
1786 return;
1789 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1790 if (CompareString (LOCALE_USER_DEFAULT,
1791 NORM_IGNORECASE | SORT_STRINGSORT,
1792 s, 2, parmP, -1) == 2) {
1793 WCHAR string[MAXSTRING];
1794 DWORD count;
1796 s += 2;
1797 while (*s && *s==' ') s++;
1799 /* If no parameter, or no '=' sign, return an error */
1800 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1801 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1802 return;
1805 /* Output the prompt */
1806 *p++ = '\0';
1807 if (strlenW(p) != 0) WCMD_output(p);
1809 /* Read the reply */
1810 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1811 sizeof(string)/sizeof(WCHAR), &count, NULL);
1812 if (count > 1) {
1813 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1814 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1815 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
1816 wine_dbgstr_w(string));
1817 status = SetEnvironmentVariable (s, string);
1820 } else {
1821 DWORD gle;
1822 p = strchrW (s, '=');
1823 if (p == NULL) {
1824 env = GetEnvironmentStrings ();
1825 if (WCMD_setshow_sortenv( env, s ) == 0) {
1826 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1827 errorlevel = 1;
1829 return;
1831 *p++ = '\0';
1833 if (strlenW(p) == 0) p = NULL;
1834 status = SetEnvironmentVariable (s, p);
1835 gle = GetLastError();
1836 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1837 errorlevel = 1;
1838 } else if ((!status)) WCMD_print_error();
1842 /****************************************************************************
1843 * WCMD_setshow_path
1845 * Set/Show the path environment variable
1848 void WCMD_setshow_path (WCHAR *command) {
1850 WCHAR string[1024];
1851 DWORD status;
1852 static const WCHAR pathW[] = {'P','A','T','H','\0'};
1853 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
1855 if (strlenW(param1) == 0) {
1856 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
1857 if (status != 0) {
1858 WCMD_output_asis ( pathEqW);
1859 WCMD_output_asis ( string);
1860 WCMD_output_asis ( newline);
1862 else {
1863 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
1866 else {
1867 if (*command == '=') command++; /* Skip leading '=' */
1868 status = SetEnvironmentVariable (pathW, command);
1869 if (!status) WCMD_print_error();
1873 /****************************************************************************
1874 * WCMD_setshow_prompt
1876 * Set or show the command prompt.
1879 void WCMD_setshow_prompt (void) {
1881 WCHAR *s;
1882 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
1884 if (strlenW(param1) == 0) {
1885 SetEnvironmentVariable (promptW, NULL);
1887 else {
1888 s = param1;
1889 while ((*s == '=') || (*s == ' ')) s++;
1890 if (strlenW(s) == 0) {
1891 SetEnvironmentVariable (promptW, NULL);
1893 else SetEnvironmentVariable (promptW, s);
1897 /****************************************************************************
1898 * WCMD_setshow_time
1900 * Set/Show the system time
1901 * FIXME: Can't change time yet
1904 void WCMD_setshow_time (void) {
1906 WCHAR curtime[64], buffer[64];
1907 DWORD count;
1908 SYSTEMTIME st;
1909 static const WCHAR parmT[] = {'/','T','\0'};
1911 if (strlenW(param1) == 0) {
1912 GetLocalTime(&st);
1913 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1914 curtime, sizeof(curtime)/sizeof(WCHAR))) {
1915 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1916 if (strstrW (quals, parmT) == NULL) {
1917 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1918 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1919 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1920 if (count > 2) {
1921 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1925 else WCMD_print_error ();
1927 else {
1928 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1932 /****************************************************************************
1933 * WCMD_shift
1935 * Shift batch parameters.
1936 * Optional /n says where to start shifting (n=0-8)
1939 void WCMD_shift (WCHAR *command) {
1940 int start;
1942 if (context != NULL) {
1943 WCHAR *pos = strchrW(command, '/');
1944 int i;
1946 if (pos == NULL) {
1947 start = 0;
1948 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
1949 start = (*(pos+1) - '0');
1950 } else {
1951 SetLastError(ERROR_INVALID_PARAMETER);
1952 WCMD_print_error();
1953 return;
1956 WINE_TRACE("Shifting variables, starting at %d\n", start);
1957 for (i=start;i<=8;i++) {
1958 context -> shift_count[i] = context -> shift_count[i+1] + 1;
1960 context -> shift_count[9] = context -> shift_count[9] + 1;
1965 /****************************************************************************
1966 * WCMD_title
1968 * Set the console title
1970 void WCMD_title (WCHAR *command) {
1971 SetConsoleTitle(command);
1974 /****************************************************************************
1975 * WCMD_type
1977 * Copy a file to standard output.
1980 void WCMD_type (WCHAR *command) {
1982 int argno = 0;
1983 WCHAR *argN = command;
1984 BOOL writeHeaders = FALSE;
1986 if (param1[0] == 0x00) {
1987 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1988 return;
1991 if (param2[0] != 0x00) writeHeaders = TRUE;
1993 /* Loop through all args */
1994 errorlevel = 0;
1995 while (argN) {
1996 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1998 HANDLE h;
1999 WCHAR buffer[512];
2000 DWORD count;
2002 if (!argN) break;
2004 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2005 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2006 FILE_ATTRIBUTE_NORMAL, NULL);
2007 if (h == INVALID_HANDLE_VALUE) {
2008 WCMD_print_error ();
2009 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2010 errorlevel = 1;
2011 } else {
2012 if (writeHeaders) {
2013 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2014 WCMD_output(fmt, thisArg);
2016 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
2017 if (count == 0) break; /* ReadFile reports success on EOF! */
2018 buffer[count] = 0;
2019 WCMD_output_asis (buffer);
2021 CloseHandle (h);
2026 /****************************************************************************
2027 * WCMD_more
2029 * Output either a file or stdin to screen in pages
2032 void WCMD_more (WCHAR *command) {
2034 int argno = 0;
2035 WCHAR *argN = command;
2036 BOOL useinput = FALSE;
2037 WCHAR moreStr[100];
2038 WCHAR moreStrPage[100];
2039 WCHAR buffer[512];
2040 DWORD count;
2041 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2042 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2043 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2044 ')',' ','-','-','\n','\0'};
2045 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2047 /* Prefix the NLS more with '-- ', then load the text */
2048 errorlevel = 0;
2049 strcpyW(moreStr, moreStart);
2050 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
2051 (sizeof(moreStr)/sizeof(WCHAR))-3);
2053 if (param1[0] == 0x00) {
2055 /* Wine implements pipes via temporary files, and hence stdin is
2056 effectively reading from the file. This means the prompts for
2057 more are satistied by the next line from the input (file). To
2058 avoid this, ensure stdin is to the console */
2059 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2060 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
2061 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2062 FILE_ATTRIBUTE_NORMAL, 0);
2063 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2065 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2066 once you get in this bit unless due to a pipe, its going to end badly... */
2067 useinput = TRUE;
2068 wsprintf(moreStrPage, moreFmt, moreStr);
2070 WCMD_enter_paged_mode(moreStrPage);
2071 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2072 if (count == 0) break; /* ReadFile reports success on EOF! */
2073 buffer[count] = 0;
2074 WCMD_output_asis (buffer);
2076 WCMD_leave_paged_mode();
2078 /* Restore stdin to what it was */
2079 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2080 CloseHandle(hConIn);
2082 return;
2083 } else {
2084 BOOL needsPause = FALSE;
2086 /* Loop through all args */
2087 WCMD_enter_paged_mode(moreStrPage);
2089 while (argN) {
2090 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2091 HANDLE h;
2093 if (!argN) break;
2095 if (needsPause) {
2097 /* Wait */
2098 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
2099 WCMD_leave_paged_mode();
2100 WCMD_output_asis(moreStrPage);
2101 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2102 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2103 WCMD_enter_paged_mode(moreStrPage);
2107 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2108 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2109 FILE_ATTRIBUTE_NORMAL, NULL);
2110 if (h == INVALID_HANDLE_VALUE) {
2111 WCMD_print_error ();
2112 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2113 errorlevel = 1;
2114 } else {
2115 ULONG64 curPos = 0;
2116 ULONG64 fileLen = 0;
2117 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2119 /* Get the file size */
2120 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2121 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2123 needsPause = TRUE;
2124 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2125 if (count == 0) break; /* ReadFile reports success on EOF! */
2126 buffer[count] = 0;
2127 curPos += count;
2129 /* Update % count (would be used in WCMD_output_asis as prompt) */
2130 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2132 WCMD_output_asis (buffer);
2134 CloseHandle (h);
2138 WCMD_leave_paged_mode();
2142 /****************************************************************************
2143 * WCMD_verify
2145 * Display verify flag.
2146 * FIXME: We don't actually do anything with the verify flag other than toggle
2147 * it...
2150 void WCMD_verify (WCHAR *command) {
2152 int count;
2154 count = strlenW(command);
2155 if (count == 0) {
2156 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2157 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2158 return;
2160 if (lstrcmpiW(command, onW) == 0) {
2161 verify_mode = 1;
2162 return;
2164 else if (lstrcmpiW(command, offW) == 0) {
2165 verify_mode = 0;
2166 return;
2168 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2171 /****************************************************************************
2172 * WCMD_version
2174 * Display version info.
2177 void WCMD_version (void) {
2179 WCMD_output (version_string);
2183 /****************************************************************************
2184 * WCMD_volume
2186 * Display volume info and/or set volume label. Returns 0 if error.
2189 int WCMD_volume (int mode, WCHAR *path) {
2191 DWORD count, serial;
2192 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2193 BOOL status;
2195 if (strlenW(path) == 0) {
2196 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2197 if (!status) {
2198 WCMD_print_error ();
2199 return 0;
2201 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2202 &serial, NULL, NULL, NULL, 0);
2204 else {
2205 static const WCHAR fmt[] = {'%','s','\\','\0'};
2206 if ((path[1] != ':') || (strlenW(path) != 2)) {
2207 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2208 return 0;
2210 wsprintf (curdir, fmt, path);
2211 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2212 &serial, NULL,
2213 NULL, NULL, 0);
2215 if (!status) {
2216 WCMD_print_error ();
2217 return 0;
2219 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2220 curdir[0], label, HIWORD(serial), LOWORD(serial));
2221 if (mode) {
2222 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2223 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2224 sizeof(string)/sizeof(WCHAR), &count, NULL);
2225 if (count > 1) {
2226 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2227 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2229 if (strlenW(path) != 0) {
2230 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2232 else {
2233 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2236 return 1;
2239 /**************************************************************************
2240 * WCMD_exit
2242 * Exit either the process, or just this batch program
2246 void WCMD_exit (CMD_LIST **cmdList) {
2248 static const WCHAR parmB[] = {'/','B','\0'};
2249 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2251 if (context && lstrcmpiW(quals, parmB) == 0) {
2252 errorlevel = rc;
2253 context -> skip_rest = TRUE;
2254 *cmdList = NULL;
2255 } else {
2256 ExitProcess(rc);
2260 /**************************************************************************
2261 * WCMD_ask_confirm
2263 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2264 * answer.
2266 * Returns True if Y (or A) answer is selected
2267 * If optionAll contains a pointer, ALL is allowed, and if answered
2268 * set to TRUE
2271 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2273 WCHAR msgbuffer[MAXSTRING];
2274 WCHAR Ybuffer[MAXSTRING];
2275 WCHAR Nbuffer[MAXSTRING];
2276 WCHAR Abuffer[MAXSTRING];
2277 WCHAR answer[MAX_PATH] = {'\0'};
2278 DWORD count = 0;
2280 /* Load the translated 'Are you sure', plus valid answers */
2281 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2282 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2283 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2284 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2286 /* Loop waiting on a Y or N */
2287 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2288 static const WCHAR startBkt[] = {' ','(','\0'};
2289 static const WCHAR endBkt[] = {')','?','\0'};
2291 WCMD_output_asis (message);
2292 if (showSureText) {
2293 WCMD_output_asis (msgbuffer);
2295 WCMD_output_asis (startBkt);
2296 WCMD_output_asis (Ybuffer);
2297 WCMD_output_asis (fslashW);
2298 WCMD_output_asis (Nbuffer);
2299 if (optionAll) {
2300 WCMD_output_asis (fslashW);
2301 WCMD_output_asis (Abuffer);
2303 WCMD_output_asis (endBkt);
2304 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2305 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2306 answer[0] = toupper(answer[0]);
2309 /* Return the answer */
2310 return ((answer[0] == Ybuffer[0]) ||
2311 (optionAll && (answer[0] == Abuffer[0])));
2314 /*****************************************************************************
2315 * WCMD_assoc
2317 * Lists or sets file associations (assoc = TRUE)
2318 * Lists or sets file types (assoc = FALSE)
2320 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2322 HKEY key;
2323 DWORD accessOptions = KEY_READ;
2324 WCHAR *newValue;
2325 LONG rc = ERROR_SUCCESS;
2326 WCHAR keyValue[MAXSTRING];
2327 DWORD valueLen = MAXSTRING;
2328 HKEY readKey;
2329 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2330 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2332 /* See if parameter includes '=' */
2333 errorlevel = 0;
2334 newValue = strchrW(command, '=');
2335 if (newValue) accessOptions |= KEY_WRITE;
2337 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2338 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2339 accessOptions, &key) != ERROR_SUCCESS) {
2340 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2341 return;
2344 /* If no parameters then list all associations */
2345 if (*command == 0x00) {
2346 int index = 0;
2348 /* Enumerate all the keys */
2349 while (rc != ERROR_NO_MORE_ITEMS) {
2350 WCHAR keyName[MAXSTRING];
2351 DWORD nameLen;
2353 /* Find the next value */
2354 nameLen = MAXSTRING;
2355 rc = RegEnumKeyEx(key, index++,
2356 keyName, &nameLen,
2357 NULL, NULL, NULL, NULL);
2359 if (rc == ERROR_SUCCESS) {
2361 /* Only interested in extension ones if assoc, or others
2362 if not assoc */
2363 if ((keyName[0] == '.' && assoc) ||
2364 (!(keyName[0] == '.') && (!assoc)))
2366 WCHAR subkey[MAXSTRING];
2367 strcpyW(subkey, keyName);
2368 if (!assoc) strcatW(subkey, shOpCmdW);
2370 if (RegOpenKeyEx(key, subkey, 0,
2371 accessOptions, &readKey) == ERROR_SUCCESS) {
2373 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2374 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2375 (LPBYTE)keyValue, &valueLen);
2376 WCMD_output_asis(keyName);
2377 WCMD_output_asis(equalW);
2378 /* If no default value found, leave line empty after '=' */
2379 if (rc == ERROR_SUCCESS) {
2380 WCMD_output_asis(keyValue);
2382 WCMD_output_asis(newline);
2387 RegCloseKey(readKey);
2389 } else {
2391 /* Parameter supplied - if no '=' on command line, its a query */
2392 if (newValue == NULL) {
2393 WCHAR *space;
2394 WCHAR subkey[MAXSTRING];
2396 /* Query terminates the parameter at the first space */
2397 strcpyW(keyValue, command);
2398 space = strchrW(keyValue, ' ');
2399 if (space) *space=0x00;
2401 /* Set up key name */
2402 strcpyW(subkey, keyValue);
2403 if (!assoc) strcatW(subkey, shOpCmdW);
2405 if (RegOpenKeyEx(key, subkey, 0,
2406 accessOptions, &readKey) == ERROR_SUCCESS) {
2408 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2409 (LPBYTE)keyValue, &valueLen);
2410 WCMD_output_asis(command);
2411 WCMD_output_asis(equalW);
2412 /* If no default value found, leave line empty after '=' */
2413 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2414 WCMD_output_asis(newline);
2415 RegCloseKey(readKey);
2417 } else {
2418 WCHAR msgbuffer[MAXSTRING];
2419 WCHAR outbuffer[MAXSTRING];
2421 /* Load the translated 'File association not found' */
2422 if (assoc) {
2423 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2424 } else {
2425 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2427 wsprintf(outbuffer, msgbuffer, keyValue);
2428 WCMD_output_asis(outbuffer);
2429 errorlevel = 2;
2432 /* Not a query - its a set or clear of a value */
2433 } else {
2435 WCHAR subkey[MAXSTRING];
2437 /* Get pointer to new value */
2438 *newValue = 0x00;
2439 newValue++;
2441 /* Set up key name */
2442 strcpyW(subkey, command);
2443 if (!assoc) strcatW(subkey, shOpCmdW);
2445 /* If nothing after '=' then clear value - only valid for ASSOC */
2446 if (*newValue == 0x00) {
2448 if (assoc) rc = RegDeleteKey(key, command);
2449 if (assoc && rc == ERROR_SUCCESS) {
2450 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2452 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2453 WCMD_print_error();
2454 errorlevel = 2;
2456 } else {
2457 WCHAR msgbuffer[MAXSTRING];
2458 WCHAR outbuffer[MAXSTRING];
2460 /* Load the translated 'File association not found' */
2461 if (assoc) {
2462 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2463 sizeof(msgbuffer)/sizeof(WCHAR));
2464 } else {
2465 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2466 sizeof(msgbuffer)/sizeof(WCHAR));
2468 wsprintf(outbuffer, msgbuffer, keyValue);
2469 WCMD_output_asis(outbuffer);
2470 errorlevel = 2;
2473 /* It really is a set value = contents */
2474 } else {
2475 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2476 accessOptions, NULL, &readKey, NULL);
2477 if (rc == ERROR_SUCCESS) {
2478 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2479 (LPBYTE)newValue, strlenW(newValue));
2480 RegCloseKey(readKey);
2483 if (rc != ERROR_SUCCESS) {
2484 WCMD_print_error();
2485 errorlevel = 2;
2486 } else {
2487 WCMD_output_asis(command);
2488 WCMD_output_asis(equalW);
2489 WCMD_output_asis(newValue);
2490 WCMD_output_asis(newline);
2496 /* Clean up */
2497 RegCloseKey(key);
2500 /****************************************************************************
2501 * WCMD_color
2503 * Clear the terminal screen.
2506 void WCMD_color (void) {
2508 /* Emulate by filling the screen from the top left to bottom right with
2509 spaces, then moving the cursor to the top left afterwards */
2510 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2511 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2513 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2514 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2515 return;
2518 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2520 COORD topLeft;
2521 DWORD screenSize;
2522 DWORD color = 0;
2524 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2526 topLeft.X = 0;
2527 topLeft.Y = 0;
2529 /* Convert the color hex digits */
2530 if (param1[0] == 0x00) {
2531 color = defaultColor;
2532 } else {
2533 color = strtoulW(param1, NULL, 16);
2536 /* Fail if fg == bg color */
2537 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2538 errorlevel = 1;
2539 return;
2542 /* Set the current screen contents and ensure all future writes
2543 remain this color */
2544 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2545 SetConsoleTextAttribute(hStdOut, color);