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
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.
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
39 #include "wine/debug.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
43 void WCMD_execute (WCHAR *orig_command, WCHAR *parameter, WCHAR *substitution, CMD_LIST **cmdList);
45 struct env_stack *saved_environment;
46 struct env_stack *pushd_directories;
48 extern HINSTANCE hinst;
49 extern WCHAR inbuilt[][10];
50 extern int echo_mode, verify_mode, defaultColor;
51 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
52 extern BATCH_CONTEXT *context;
53 extern DWORD errorlevel;
55 static const WCHAR dotW[] = {'.','\0'};
56 static const WCHAR dotdotW[] = {'.','.','\0'};
57 static const WCHAR slashW[] = {'\\','\0'};
58 static const WCHAR starW[] = {'*','\0'};
59 static const WCHAR equalW[] = {'=','\0'};
60 static const WCHAR fslashW[] = {'/','\0'};
61 static const WCHAR onW[] = {'O','N','\0'};
62 static const WCHAR offW[] = {'O','F','F','\0'};
63 static const WCHAR parmY[] = {'/','Y','\0'};
64 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
65 static const WCHAR nullW[] = {'\0'};
67 /****************************************************************************
70 * Clear the terminal screen.
73 void WCMD_clear_screen (void) {
75 /* Emulate by filling the screen from the top left to bottom right with
76 spaces, then moving the cursor to the top left afterwards */
77 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
78 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
80 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
85 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
89 FillConsoleOutputCharacter(hStdOut, ' ', screenSize, topLeft, &screenSize);
90 SetConsoleCursorPosition(hStdOut, topLeft);
94 /****************************************************************************
97 * Change the default i/o device (ie redirect STDin/STDout).
100 void WCMD_change_tty (void) {
102 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
106 /****************************************************************************
109 * Copy a file or wildcarded set.
110 * FIXME: No wildcard support
113 void WCMD_copy (void) {
118 WCHAR outpath[MAX_PATH], inpath[MAX_PATH], *infile, copycmd[3];
120 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
122 if (param1[0] == 0x00) {
123 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
127 if ((strchrW(param1,'*') != NULL) && (strchrW(param1,'%') != NULL)) {
128 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
132 /* If no destination supplied, assume current directory */
133 if (param2[0] == 0x00) {
134 strcpyW(param2, dotW);
137 GetFullPathName (param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
138 if (outpath[strlenW(outpath) - 1] == '\\')
139 outpath[strlenW(outpath) - 1] = '\0';
140 hff = FindFirstFile (outpath, &fd);
141 if (hff != INVALID_HANDLE_VALUE) {
142 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
143 GetFullPathName (param1, sizeof(inpath)/sizeof(WCHAR), inpath, &infile);
144 strcatW (outpath, slashW);
145 strcatW (outpath, infile);
150 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
151 if (strstrW (quals, parmNoY))
153 else if (strstrW (quals, parmY))
156 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
157 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR)) && ! lstrcmpiW (copycmd, parmY));
161 hff = FindFirstFile (outpath, &fd);
162 if (hff != INVALID_HANDLE_VALUE) {
163 WCHAR buffer[MAXSTRING];
167 wsprintf(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outpath);
168 force = WCMD_ask_confirm(buffer, FALSE, NULL);
173 status = CopyFile (param1, outpath, FALSE);
174 if (!status) WCMD_print_error ();
178 /****************************************************************************
181 * Create a directory.
183 * this works recursivly. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
184 * they do not already exist.
187 static BOOL create_full_path(WCHAR* path)
193 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path) * sizeof(WCHAR))+1);
194 strcpyW(new_path,path);
196 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
197 new_path[len - 1] = 0;
199 while (!CreateDirectory(new_path,NULL))
202 DWORD last_error = GetLastError();
203 if (last_error == ERROR_ALREADY_EXISTS)
206 if (last_error != ERROR_PATH_NOT_FOUND)
212 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
218 len = slash - new_path;
220 if (!create_full_path(new_path))
225 new_path[len] = '\\';
227 HeapFree(GetProcessHeap(),0,new_path);
231 void WCMD_create_dir (void) {
233 if (param1[0] == 0x00) {
234 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
237 if (!create_full_path(param1)) WCMD_print_error ();
240 /****************************************************************************
243 * Delete a file or wildcarded set.
246 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
247 * - Each set is a pattern, eg /ahr /as-r means
248 * readonly+hidden OR nonreadonly system files
249 * - The '-' applies to a single field, ie /a:-hr means read only
253 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
256 int argsProcessed = 0;
257 WCHAR *argN = command;
258 BOOL foundAny = FALSE;
259 static const WCHAR parmA[] = {'/','A','\0'};
260 static const WCHAR parmQ[] = {'/','Q','\0'};
261 static const WCHAR parmP[] = {'/','P','\0'};
262 static const WCHAR parmS[] = {'/','S','\0'};
263 static const WCHAR parmF[] = {'/','F','\0'};
265 /* If not recursing, clear error flag */
266 if (expectDir) errorlevel = 0;
268 /* Loop through all args */
270 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
271 WCHAR argCopy[MAX_PATH];
273 if (argN && argN[0] != '/') {
277 WCHAR fpath[MAX_PATH];
279 BOOL handleParm = TRUE;
281 static const WCHAR anyExt[]= {'.','*','\0'};
283 strcpyW(argCopy, thisArg);
284 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
285 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
288 /* If filename part of parameter is * or *.*, prompt unless
290 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
294 WCHAR fname[MAX_PATH];
297 /* Convert path into actual directory spec */
298 GetFullPathName (argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
299 WCMD_splitpath(fpath, drive, dir, fname, ext);
301 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
302 if ((strcmpW(fname, starW) == 0) &&
303 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
305 WCHAR question[MAXSTRING];
306 static const WCHAR fmt[] = {'%','s',' ','\0'};
308 /* Note: Flag as found, to avoid file not found message */
311 /* Ask for confirmation */
312 wsprintf(question, fmt, fpath);
313 ok = WCMD_ask_confirm(question, TRUE, NULL);
315 /* Abort if answer is 'N' */
320 /* First, try to delete in the current directory */
321 hff = FindFirstFile (argCopy, &fd);
322 if (hff == INVALID_HANDLE_VALUE) {
328 /* Support del <dirname> by just deleting all files dirname\* */
329 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
330 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
331 WCHAR modifiedParm[MAX_PATH];
332 static const WCHAR slashStar[] = {'\\','*','\0'};
334 strcpyW(modifiedParm, argCopy);
335 strcatW(modifiedParm, slashStar);
338 WCMD_delete(modifiedParm, FALSE);
340 } else if (handleParm) {
342 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
343 strcpyW (fpath, argCopy);
345 p = strrchrW (fpath, '\\');
348 strcatW (fpath, fd.cFileName);
350 else strcpyW (fpath, fd.cFileName);
351 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
353 WCHAR *nextA = strstrW (quals, parmA);
355 /* Handle attribute matching (/A) */
358 while (nextA != NULL && !ok) {
360 WCHAR *thisA = (nextA+2);
363 /* Skip optional : */
364 if (*thisA == ':') thisA++;
366 /* Parse each of the /A[:]xxx in turn */
367 while (*thisA && *thisA != '/') {
369 BOOL attribute = FALSE;
371 /* Match negation of attribute first */
377 /* Match attribute */
379 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
381 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
383 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
385 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
388 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
391 /* Now check result, keeping a running boolean about whether it
392 matches all parsed attribues so far */
393 if (attribute && !negate) {
395 } else if (!attribute && negate) {
403 /* Save the running total as the final result */
406 /* Step on to next /A set */
407 nextA = strstrW (nextA+1, parmA);
411 /* /P means prompt for each file */
412 if (ok && strstrW (quals, parmP) != NULL) {
413 WCHAR question[MAXSTRING];
415 /* Ask for confirmation */
416 wsprintf(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
417 ok = WCMD_ask_confirm(question, FALSE, NULL);
420 /* Only proceed if ok to */
423 /* If file is read only, and /F supplied, delete it */
424 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
425 strstrW (quals, parmF) != NULL) {
426 SetFileAttributes(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
429 /* Now do the delete */
430 if (!DeleteFile (fpath)) WCMD_print_error ();
434 } while (FindNextFile(hff, &fd) != 0);
438 /* Now recurse into all subdirectories handling the parameter in the same way */
439 if (strstrW (quals, parmS) != NULL) {
441 WCHAR thisDir[MAX_PATH];
446 WCHAR fname[MAX_PATH];
449 /* Convert path into actual directory spec */
450 GetFullPathName (argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
451 WCMD_splitpath(thisDir, drive, dir, fname, ext);
453 strcpyW(thisDir, drive);
454 strcatW(thisDir, dir);
455 cPos = strlenW(thisDir);
457 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
459 /* Append '*' to the directory */
461 thisDir[cPos+1] = 0x00;
463 hff = FindFirstFile (thisDir, &fd);
465 /* Remove residual '*' */
466 thisDir[cPos] = 0x00;
468 if (hff != INVALID_HANDLE_VALUE) {
469 DIRECTORY_STACK *allDirs = NULL;
470 DIRECTORY_STACK *lastEntry = NULL;
473 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
474 (strcmpW(fd.cFileName, dotdotW) != 0) &&
475 (strcmpW(fd.cFileName, dotW) != 0)) {
477 DIRECTORY_STACK *nextDir;
478 WCHAR subParm[MAX_PATH];
480 /* Work out search parameter in sub dir */
481 strcpyW (subParm, thisDir);
482 strcatW (subParm, fd.cFileName);
483 strcatW (subParm, slashW);
484 strcatW (subParm, fname);
485 strcatW (subParm, ext);
486 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
488 /* Allocate memory, add to list */
489 nextDir = (DIRECTORY_STACK *) HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
490 if (allDirs == NULL) allDirs = nextDir;
491 if (lastEntry != NULL) lastEntry->next = nextDir;
493 nextDir->next = NULL;
494 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
495 (strlenW(subParm)+1) * sizeof(WCHAR));
496 strcpyW(nextDir->dirName, subParm);
498 } while (FindNextFile(hff, &fd) != 0);
501 /* Go through each subdir doing the delete */
502 while (allDirs != NULL) {
503 DIRECTORY_STACK *tempDir;
505 tempDir = allDirs->next;
506 found |= WCMD_delete (allDirs->dirName, FALSE);
508 HeapFree(GetProcessHeap(),0,allDirs->dirName);
509 HeapFree(GetProcessHeap(),0,allDirs);
514 /* Keep running total to see if any found, and if not recursing
515 issue error message */
519 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
526 /* Handle no valid args */
527 if (argsProcessed == 0) {
528 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
534 /****************************************************************************
537 * Echo input to the screen (or not). We don't try to emulate the bugs
538 * in DOS (try typing "ECHO ON AGAIN" for an example).
541 void WCMD_echo (const WCHAR *command) {
545 if ((command[0] == '.') && (command[1] == 0)) {
546 WCMD_output (newline);
551 count = strlenW(command);
553 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
554 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
557 if (lstrcmpiW(command, onW) == 0) {
561 if (lstrcmpiW(command, offW) == 0) {
565 WCMD_output_asis (command);
566 WCMD_output (newline);
570 /**************************************************************************
573 * Batch file loop processing.
574 * FIXME: We don't exhaustively check syntax. Any command which works in MessDOS
575 * will probably work here, but the reverse is not necessarily the case...
578 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
583 WCHAR set[MAX_PATH], param[MAX_PATH];
585 const WCHAR inW[] = {'i', 'n', '\0'};
586 const WCHAR doW[] = {'d', 'o', '\0'};
588 if (lstrcmpiW (WCMD_parameter (p, 1, NULL), inW)
589 || lstrcmpiW (WCMD_parameter (p, 3, NULL), doW)
590 || (param1[0] != '%')) {
591 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
594 lstrcpynW (set, WCMD_parameter (p, 2, NULL), sizeof(set)/sizeof(WCHAR));
595 WCMD_parameter (p, 4, &cmd);
596 strcpyW (param, param1);
599 * If the parameter within the set has a wildcard then search for matching files
600 * otherwise do a literal substitution.
604 while (*(item = WCMD_parameter (set, i, NULL))) {
605 static const WCHAR wildcards[] = {'*','?','\0'};
606 if (strpbrkW (item, wildcards)) {
607 hff = FindFirstFile (item, &fd);
608 if (hff == INVALID_HANDLE_VALUE) {
612 WCMD_execute (cmd, param, fd.cFileName, cmdList);
613 } while (FindNextFile(hff, &fd) != 0);
617 WCMD_execute (cmd, param, item, cmdList);
623 /*****************************************************************************
626 * Execute a command after substituting variable text for the supplied parameter
629 void WCMD_execute (WCHAR *orig_cmd, WCHAR *param, WCHAR *subst, CMD_LIST **cmdList) {
631 WCHAR *new_cmd, *p, *s, *dup;
634 size = strlenW (orig_cmd);
635 new_cmd = (WCHAR *) LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, size);
636 dup = s = WCMD_strdupW(orig_cmd);
638 while ((p = strstrW (s, param))) {
640 size += strlenW (subst);
641 new_cmd = (WCHAR *) LocalReAlloc ((HANDLE)new_cmd, size, 0);
642 strcatW (new_cmd, s);
643 strcatW (new_cmd, subst);
644 s = p + strlenW (param);
646 strcatW (new_cmd, s);
647 WCMD_process_command (new_cmd, cmdList);
649 LocalFree ((HANDLE)new_cmd);
653 /**************************************************************************
656 * Simple on-line help. Help text is stored in the resource file.
659 void WCMD_give_help (WCHAR *command) {
663 command = WCMD_strtrim_leading_spaces(command);
664 if (strlenW(command) == 0) {
665 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
668 for (i=0; i<=WCMD_EXIT; i++) {
669 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
670 param1, -1, inbuilt[i], -1) == 2) {
671 WCMD_output_asis (WCMD_LoadMessage(i));
675 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), param1);
680 /****************************************************************************
683 * Batch file jump instruction. Not the most efficient algorithm ;-)
684 * Prints error message if the specified label cannot be found - the file pointer is
685 * then at EOF, effectively stopping the batch file.
686 * FIXME: DOS is supposed to allow labels with spaces - we don't.
689 void WCMD_goto (CMD_LIST **cmdList) {
691 WCHAR string[MAX_PATH];
693 /* Do not process any more parts of a processed multipart or multilines command */
696 if (param1[0] == 0x00) {
697 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
700 if (context != NULL) {
701 WCHAR *paramStart = param1;
702 static const WCHAR eofW[] = {':','e','o','f','\0'};
704 /* Handle special :EOF label */
705 if (lstrcmpiW (eofW, param1) == 0) {
706 context -> skip_rest = TRUE;
710 /* Support goto :label as well as goto label */
711 if (*paramStart == ':') paramStart++;
713 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
714 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
715 if ((string[0] == ':') && (lstrcmpiW (&string[1], paramStart) == 0)) return;
717 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
722 /*****************************************************************************
725 * Push a directory onto the stack
728 void WCMD_pushd (WCHAR *command) {
729 struct env_stack *curdir;
731 static const WCHAR parmD[] = {'/','D','\0'};
733 if (strchrW(command, '/') != NULL) {
734 SetLastError(ERROR_INVALID_PARAMETER);
739 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
740 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
741 if( !curdir || !thisdir ) {
744 WINE_ERR ("out of memory\n");
748 /* Change directory using CD code with /D parameter */
749 strcpyW(quals, parmD);
750 GetCurrentDirectoryW (1024, thisdir);
752 WCMD_setshow_default(command);
758 curdir -> next = pushd_directories;
759 curdir -> strings = thisdir;
760 if (pushd_directories == NULL) {
761 curdir -> u.stackdepth = 1;
763 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
765 pushd_directories = curdir;
770 /*****************************************************************************
773 * Pop a directory from the stack
776 void WCMD_popd (void) {
777 struct env_stack *temp = pushd_directories;
779 if (!pushd_directories)
782 /* pop the old environment from the stack, and make it the current dir */
783 pushd_directories = temp->next;
784 SetCurrentDirectoryW(temp->strings);
785 LocalFree (temp->strings);
789 /****************************************************************************
792 * Batch file conditional.
794 * On entry, cmdlist will point to command containing the IF, and optionally
795 * the first command to execute (if brackets not found)
796 * If &&'s were found, this may be followed by a record flagged as isAmpersand
797 * If ('s were found, execute all within that bracket
798 * Command may optionally be followed by an ELSE - need to skip instructions
799 * in the else using the same logic
801 * FIXME: Much more syntax checking needed!
804 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
806 int negate = 0, test = 0;
807 WCHAR condition[MAX_PATH], *command, *s;
808 static const WCHAR notW[] = {'n','o','t','\0'};
809 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
810 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
811 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
812 static const WCHAR eqeqW[] = {'=','=','\0'};
813 CMD_LIST *curPosition;
816 if (!lstrcmpiW (param1, notW)) {
818 strcpyW (condition, param2);
821 strcpyW (condition, param1);
823 if (!lstrcmpiW (condition, errlvlW)) {
824 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
825 WCMD_parameter (p, 2+negate, &command);
827 else if (!lstrcmpiW (condition, existW)) {
828 if (GetFileAttributes(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
831 WCMD_parameter (p, 2+negate, &command);
833 else if (!lstrcmpiW (condition, defdW)) {
834 if (GetEnvironmentVariable(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
837 WCMD_parameter (p, 2+negate, &command);
839 else if ((s = strstrW (p, eqeqW))) {
841 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
842 WCMD_parameter (s, 1, &command);
845 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
849 /* Process rest of IF statement which is on the same line
850 Note: This may process all or some of the cmdList (eg a GOTO) */
851 curPosition = *cmdList;
852 myDepth = (*cmdList)->bracketDepth;
854 if (test != negate) {
855 WCHAR *cmd = command;
857 /* Skip leading whitespace between condition and the command */
858 while (cmd && *cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
861 command = WCMD_strdupW(cmd);
862 WCMD_process_command (command, cmdList);
867 /* If it didnt move the position, step to next command */
868 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
870 /* Process any other parts of the IF */
872 BOOL processThese = (test != negate);
875 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
877 /* execute all appropriate commands */
878 curPosition = *cmdList;
880 WINE_TRACE("Processing cmdList(%p) - &(%d) bd(%d / %d)\n",
882 (*cmdList)->isAmphersand,
883 (*cmdList)->bracketDepth, myDepth);
885 /* Execute any appended to the statement with &&'s */
886 if ((*cmdList)->isAmphersand) {
888 WCMD_process_command((*cmdList)->command, cmdList);
890 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
892 /* Execute any appended to the statement with (...) */
893 } else if ((*cmdList)->bracketDepth > myDepth) {
895 *cmdList = WCMD_process_commands(*cmdList, TRUE);
896 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
898 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
900 /* End of the command - does 'ELSE ' follow as the next command? */
902 if (CompareString (LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
903 (*cmdList)->command, 5, ifElse, -1) == 2) {
905 /* Swap between if and else processing */
906 processThese = !processThese;
908 /* Process the ELSE part */
910 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
912 /* Skip leading whitespace between condition and the command */
913 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
915 WCMD_process_command(cmd, cmdList);
918 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
920 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
928 /****************************************************************************
931 * Move a file, directory tree or wildcarded set of files.
934 void WCMD_move (void) {
939 WCHAR input[MAX_PATH];
940 WCHAR output[MAX_PATH];
943 WCHAR fname[MAX_PATH];
946 if (param1[0] == 0x00) {
947 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
951 /* If no destination supplied, assume current directory */
952 if (param2[0] == 0x00) {
953 strcpyW(param2, dotW);
956 /* If 2nd parm is directory, then use original filename */
957 /* Convert partial path to full path */
958 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
959 GetFullPathName (param2, sizeof(output)/sizeof(WCHAR), output, NULL);
960 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
961 wine_dbgstr_w(param1), wine_dbgstr_w(output));
963 /* Split into components */
964 WCMD_splitpath(input, drive, dir, fname, ext);
966 hff = FindFirstFile (input, &fd);
967 while (hff != INVALID_HANDLE_VALUE) {
968 WCHAR dest[MAX_PATH];
972 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
974 /* Build src & dest name */
978 /* See if dest is an existing directory */
979 attribs = GetFileAttributes(output);
980 if (attribs != INVALID_FILE_ATTRIBUTES &&
981 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
982 strcpyW(dest, output);
983 strcatW(dest, slashW);
984 strcatW(dest, fd.cFileName);
986 strcpyW(dest, output);
989 strcatW(src, fd.cFileName);
991 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
992 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
994 /* Check if file is read only, otherwise move it */
995 attribs = GetFileAttributes(src);
996 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
997 (attribs & FILE_ATTRIBUTE_READONLY)) {
998 SetLastError(ERROR_ACCESS_DENIED);
1003 /* If destination exists, prompt unless /Y supplied */
1004 if (GetFileAttributes(dest) != INVALID_FILE_ATTRIBUTES) {
1006 WCHAR copycmd[MAXSTRING];
1009 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1010 if (strstrW (quals, parmNoY))
1012 else if (strstrW (quals, parmY))
1015 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1016 len = GetEnvironmentVariable (copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1017 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1018 && ! lstrcmpiW (copycmd, parmY));
1021 /* Prompt if overwriting */
1023 WCHAR question[MAXSTRING];
1026 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1028 /* Ask for confirmation */
1029 wsprintf(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1030 ok = WCMD_ask_confirm(question, FALSE, NULL);
1032 /* So delete the destination prior to the move */
1034 if (!DeleteFile (dest)) {
1035 WCMD_print_error ();
1044 status = MoveFile (src, dest);
1046 status = 1; /* Anything other than 0 to prevent error msg below */
1051 WCMD_print_error ();
1055 /* Step on to next match */
1056 if (FindNextFile(hff, &fd) == 0) {
1058 hff = INVALID_HANDLE_VALUE;
1064 /****************************************************************************
1067 * Wait for keyboard input.
1070 void WCMD_pause (void) {
1075 WCMD_output (anykey);
1076 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1077 sizeof(string)/sizeof(WCHAR), &count, NULL);
1080 /****************************************************************************
1083 * Delete a directory.
1086 void WCMD_remove_dir (WCHAR *command) {
1089 int argsProcessed = 0;
1090 WCHAR *argN = command;
1091 static const WCHAR parmS[] = {'/','S','\0'};
1092 static const WCHAR parmQ[] = {'/','Q','\0'};
1094 /* Loop through all args */
1096 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1097 if (argN && argN[0] != '/') {
1098 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1099 wine_dbgstr_w(quals));
1102 /* If subdirectory search not supplied, just try to remove
1103 and report error if it fails (eg if it contains a file) */
1104 if (strstrW (quals, parmS) == NULL) {
1105 if (!RemoveDirectory (thisArg)) WCMD_print_error ();
1107 /* Otherwise use ShFileOp to recursively remove a directory */
1110 SHFILEOPSTRUCT lpDir;
1113 if (strstrW (quals, parmQ) == NULL) {
1115 WCHAR question[MAXSTRING];
1116 static const WCHAR fmt[] = {'%','s',' ','\0'};
1118 /* Ask for confirmation */
1119 wsprintf(question, fmt, thisArg);
1120 ok = WCMD_ask_confirm(question, TRUE, NULL);
1122 /* Abort if answer is 'N' */
1129 lpDir.pFrom = thisArg;
1130 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1131 lpDir.wFunc = FO_DELETE;
1132 if (SHFileOperation(&lpDir)) WCMD_print_error ();
1137 /* Handle no valid args */
1138 if (argsProcessed == 0) {
1139 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1145 /****************************************************************************
1151 void WCMD_rename (void) {
1156 WCHAR input[MAX_PATH];
1157 WCHAR *dotDst = NULL;
1159 WCHAR dir[MAX_PATH];
1160 WCHAR fname[MAX_PATH];
1161 WCHAR ext[MAX_PATH];
1166 /* Must be at least two args */
1167 if (param1[0] == 0x00 || param2[0] == 0x00) {
1168 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1173 /* Destination cannot contain a drive letter or directory separator */
1174 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1175 SetLastError(ERROR_INVALID_PARAMETER);
1181 /* Convert partial path to full path */
1182 GetFullPathName (param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1183 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1184 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1185 dotDst = strchrW(param2, '.');
1187 /* Split into components */
1188 WCMD_splitpath(input, drive, dir, fname, ext);
1190 hff = FindFirstFile (input, &fd);
1191 while (hff != INVALID_HANDLE_VALUE) {
1192 WCHAR dest[MAX_PATH];
1193 WCHAR src[MAX_PATH];
1194 WCHAR *dotSrc = NULL;
1197 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1199 /* FIXME: If dest name or extension is *, replace with filename/ext
1200 part otherwise use supplied name. This supports:
1202 ren jim.* fred.* etc
1203 However, windows has a more complex algorithum supporting eg
1204 ?'s and *'s mid name */
1205 dotSrc = strchrW(fd.cFileName, '.');
1207 /* Build src & dest name */
1208 strcpyW(src, drive);
1211 dirLen = strlenW(src);
1212 strcatW(src, fd.cFileName);
1215 if (param2[0] == '*') {
1216 strcatW(dest, fd.cFileName);
1217 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1219 strcatW(dest, param2);
1220 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1223 /* Build Extension */
1224 if (dotDst && (*(dotDst+1)=='*')) {
1225 if (dotSrc) strcatW(dest, dotSrc);
1226 } else if (dotDst) {
1227 if (dotDst) strcatW(dest, dotDst);
1230 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1231 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1233 /* Check if file is read only, otherwise move it */
1234 attribs = GetFileAttributes(src);
1235 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1236 (attribs & FILE_ATTRIBUTE_READONLY)) {
1237 SetLastError(ERROR_ACCESS_DENIED);
1240 status = MoveFile (src, dest);
1244 WCMD_print_error ();
1248 /* Step on to next match */
1249 if (FindNextFile(hff, &fd) == 0) {
1251 hff = INVALID_HANDLE_VALUE;
1257 /*****************************************************************************
1260 * Make a copy of the environment.
1262 static WCHAR *WCMD_dupenv( const WCHAR *env )
1272 len += (strlenW(&env[len]) + 1);
1274 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1277 WINE_ERR("out of memory\n");
1280 memcpy (env_copy, env, len*sizeof (WCHAR));
1286 /*****************************************************************************
1289 * setlocal pushes the environment onto a stack
1290 * Save the environment as unicode so we don't screw anything up.
1292 void WCMD_setlocal (const WCHAR *s) {
1294 struct env_stack *env_copy;
1295 WCHAR cwd[MAX_PATH];
1297 /* DISABLEEXTENSIONS ignored */
1299 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1302 WINE_ERR ("out of memory\n");
1306 env = GetEnvironmentStringsW ();
1308 env_copy->strings = WCMD_dupenv (env);
1309 if (env_copy->strings)
1311 env_copy->next = saved_environment;
1312 saved_environment = env_copy;
1314 /* Save the current drive letter */
1315 GetCurrentDirectory (MAX_PATH, cwd);
1316 env_copy->u.cwd = cwd[0];
1319 LocalFree (env_copy);
1321 FreeEnvironmentStringsW (env);
1325 /*****************************************************************************
1328 * endlocal pops the environment off a stack
1329 * Note: When searching for '=', search from WCHAR position 1, to handle
1330 * special internal environment variables =C:, =D: etc
1332 void WCMD_endlocal (void) {
1333 WCHAR *env, *old, *p;
1334 struct env_stack *temp;
1337 if (!saved_environment)
1340 /* pop the old environment from the stack */
1341 temp = saved_environment;
1342 saved_environment = temp->next;
1344 /* delete the current environment, totally */
1345 env = GetEnvironmentStringsW ();
1346 old = WCMD_dupenv (GetEnvironmentStringsW ());
1349 n = strlenW(&old[len]) + 1;
1350 p = strchrW(&old[len] + 1, '=');
1354 SetEnvironmentVariableW (&old[len], NULL);
1359 FreeEnvironmentStringsW (env);
1361 /* restore old environment */
1362 env = temp->strings;
1365 n = strlenW(&env[len]) + 1;
1366 p = strchrW(&env[len] + 1, '=');
1370 SetEnvironmentVariableW (&env[len], p);
1375 /* Restore current drive letter */
1376 if (IsCharAlpha(temp->u.cwd)) {
1378 WCHAR cwd[MAX_PATH];
1379 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1381 wsprintf(envvar, fmt, temp->u.cwd);
1382 if (GetEnvironmentVariable(envvar, cwd, MAX_PATH)) {
1383 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1384 SetCurrentDirectory(cwd);
1392 /*****************************************************************************
1393 * WCMD_setshow_attrib
1395 * Display and optionally sets DOS attributes on a file or directory
1397 * FIXME: Wine currently uses the Unix stat() function to get file attributes.
1398 * As a result only the Readonly flag is correctly reported, the Archive bit
1399 * is always set and the rest are not implemented. We do the Right Thing anyway.
1401 * FIXME: No SET functionality.
1405 void WCMD_setshow_attrib (void) {
1410 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1412 if (param1[0] == '-') {
1413 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1417 if (strlenW(param1) == 0) {
1418 static const WCHAR slashStarW[] = {'\\','*','\0'};
1420 GetCurrentDirectory (sizeof(param1)/sizeof(WCHAR), param1);
1421 strcatW (param1, slashStarW);
1424 hff = FindFirstFile (param1, &fd);
1425 if (hff == INVALID_HANDLE_VALUE) {
1426 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), param1);
1430 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
1431 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1432 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1435 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1438 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1441 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1444 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1447 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1450 WCMD_output (fmt, flags, fd.cFileName);
1451 for (count=0; count < 8; count++) flags[count] = ' ';
1453 } while (FindNextFile(hff, &fd) != 0);
1458 /*****************************************************************************
1459 * WCMD_setshow_default
1461 * Set/Show the current default directory
1464 void WCMD_setshow_default (WCHAR *command) {
1472 static const WCHAR parmD[] = {'/','D','\0'};
1474 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1476 /* Skip /D and trailing whitespace if on the front of the command line */
1477 if (CompareString (LOCALE_USER_DEFAULT,
1478 NORM_IGNORECASE | SORT_STRINGSORT,
1479 command, 2, parmD, -1) == 2) {
1481 while (*command && *command==' ') command++;
1484 GetCurrentDirectory (sizeof(cwd)/sizeof(WCHAR), cwd);
1485 if (strlenW(command) == 0) {
1486 strcatW (cwd, newline);
1490 /* Remove any double quotes, which may be in the
1491 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1494 if (*command != '"') *pos++ = *command;
1499 /* Search for approprate directory */
1500 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1501 hff = FindFirstFile (string, &fd);
1502 while (hff != INVALID_HANDLE_VALUE) {
1503 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1504 WCHAR fpath[MAX_PATH];
1506 WCHAR dir[MAX_PATH];
1507 WCHAR fname[MAX_PATH];
1508 WCHAR ext[MAX_PATH];
1509 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1511 /* Convert path into actual directory spec */
1512 GetFullPathName (string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1513 WCMD_splitpath(fpath, drive, dir, fname, ext);
1516 wsprintf(string, fmt, drive, dir, fd.cFileName);
1519 hff = INVALID_HANDLE_VALUE;
1523 /* Step on to next match */
1524 if (FindNextFile(hff, &fd) == 0) {
1526 hff = INVALID_HANDLE_VALUE;
1531 /* Change to that directory */
1532 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1534 status = SetCurrentDirectory (string);
1537 WCMD_print_error ();
1541 /* Restore old directory if drive letter would change, and
1542 CD x:\directory /D (or pushd c:\directory) not supplied */
1543 if ((strstrW(quals, parmD) == NULL) &&
1544 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1545 SetCurrentDirectory(cwd);
1549 /* Set special =C: type environment variable, for drive letter of
1550 change of directory, even if path was restored due to missing
1551 /D (allows changing drive letter when not resident on that
1553 if ((string[1] == ':') && IsCharAlpha (string[0])) {
1555 strcpyW(env, equalW);
1556 memcpy(env+1, string, 2 * sizeof(WCHAR));
1558 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1559 SetEnvironmentVariable(env, string);
1566 /****************************************************************************
1569 * Set/Show the system date
1570 * FIXME: Can't change date yet
1573 void WCMD_setshow_date (void) {
1575 WCHAR curdate[64], buffer[64];
1577 static const WCHAR parmT[] = {'/','T','\0'};
1579 if (strlenW(param1) == 0) {
1580 if (GetDateFormat (LOCALE_USER_DEFAULT, 0, NULL, NULL,
1581 curdate, sizeof(curdate)/sizeof(WCHAR))) {
1582 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
1583 if (strstrW (quals, parmT) == NULL) {
1584 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
1585 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
1586 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1588 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1592 else WCMD_print_error ();
1595 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1599 /****************************************************************************
1602 static int WCMD_compare( const void *a, const void *b )
1605 const WCHAR * const *str_a = a, * const *str_b = b;
1606 r = CompareString( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1607 *str_a, -1, *str_b, -1 );
1608 if( r == CSTR_LESS_THAN ) return -1;
1609 if( r == CSTR_GREATER_THAN ) return 1;
1613 /****************************************************************************
1614 * WCMD_setshow_sortenv
1616 * sort variables into order for display
1617 * Optionally only display those who start with a stub
1618 * returns the count displayed
1620 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
1622 UINT count=0, len=0, i, displayedcount=0, stublen=0;
1625 if (stub) stublen = strlenW(stub);
1627 /* count the number of strings, and the total length */
1629 len += (strlenW(&s[len]) + 1);
1633 /* add the strings to an array */
1634 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
1638 for( i=1; i<count; i++ )
1639 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
1641 /* sort the array */
1642 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
1645 for( i=0; i<count; i++ ) {
1646 if (!stub || CompareString (LOCALE_USER_DEFAULT,
1647 NORM_IGNORECASE | SORT_STRINGSORT,
1648 str[i], stublen, stub, -1) == 2) {
1649 /* Don't display special internal variables */
1650 if (str[i][0] != '=') {
1651 WCMD_output_asis(str[i]);
1652 WCMD_output_asis(newline);
1659 return displayedcount;
1662 /****************************************************************************
1665 * Set/Show the environment variables
1668 void WCMD_setshow_env (WCHAR *s) {
1673 static const WCHAR parmP[] = {'/','P','\0'};
1676 if (param1[0] == 0x00 && quals[0] == 0x00) {
1677 env = GetEnvironmentStrings ();
1678 WCMD_setshow_sortenv( env, NULL );
1682 /* See if /P supplied, and if so echo the prompt, and read in a reply */
1683 if (CompareString (LOCALE_USER_DEFAULT,
1684 NORM_IGNORECASE | SORT_STRINGSORT,
1685 s, 2, parmP, -1) == 2) {
1686 WCHAR string[MAXSTRING];
1690 while (*s && *s==' ') s++;
1692 /* If no parameter, or no '=' sign, return an error */
1693 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
1694 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1698 /* Output the prompt */
1700 if (strlenW(p) != 0) WCMD_output(p);
1702 /* Read the reply */
1703 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1704 sizeof(string)/sizeof(WCHAR), &count, NULL);
1706 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
1707 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
1708 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
1709 wine_dbgstr_w(string));
1710 status = SetEnvironmentVariable (s, string);
1715 p = strchrW (s, '=');
1717 env = GetEnvironmentStrings ();
1718 if (WCMD_setshow_sortenv( env, s ) == 0) {
1719 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
1726 if (strlenW(p) == 0) p = NULL;
1727 status = SetEnvironmentVariable (s, p);
1728 gle = GetLastError();
1729 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
1731 } else if ((!status)) WCMD_print_error();
1735 /****************************************************************************
1738 * Set/Show the path environment variable
1741 void WCMD_setshow_path (WCHAR *command) {
1745 static const WCHAR pathW[] = {'P','A','T','H','\0'};
1746 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
1748 if (strlenW(param1) == 0) {
1749 status = GetEnvironmentVariable (pathW, string, sizeof(string)/sizeof(WCHAR));
1751 WCMD_output_asis ( pathEqW);
1752 WCMD_output_asis ( string);
1753 WCMD_output_asis ( newline);
1756 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
1760 if (*command == '=') command++; /* Skip leading '=' */
1761 status = SetEnvironmentVariable (pathW, command);
1762 if (!status) WCMD_print_error();
1766 /****************************************************************************
1767 * WCMD_setshow_prompt
1769 * Set or show the command prompt.
1772 void WCMD_setshow_prompt (void) {
1775 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
1777 if (strlenW(param1) == 0) {
1778 SetEnvironmentVariable (promptW, NULL);
1782 while ((*s == '=') || (*s == ' ')) s++;
1783 if (strlenW(s) == 0) {
1784 SetEnvironmentVariable (promptW, NULL);
1786 else SetEnvironmentVariable (promptW, s);
1790 /****************************************************************************
1793 * Set/Show the system time
1794 * FIXME: Can't change time yet
1797 void WCMD_setshow_time (void) {
1799 WCHAR curtime[64], buffer[64];
1802 static const WCHAR parmT[] = {'/','T','\0'};
1804 if (strlenW(param1) == 0) {
1806 if (GetTimeFormat (LOCALE_USER_DEFAULT, 0, &st, NULL,
1807 curtime, sizeof(curtime)/sizeof(WCHAR))) {
1808 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curtime);
1809 if (strstrW (quals, parmT) == NULL) {
1810 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
1811 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1812 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1814 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1818 else WCMD_print_error ();
1821 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1825 /****************************************************************************
1828 * Shift batch parameters.
1829 * Optional /n says where to start shifting (n=0-8)
1832 void WCMD_shift (WCHAR *command) {
1835 if (context != NULL) {
1836 WCHAR *pos = strchrW(command, '/');
1841 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
1842 start = (*(pos+1) - '0');
1844 SetLastError(ERROR_INVALID_PARAMETER);
1849 WINE_TRACE("Shifting variables, starting at %d\n", start);
1850 for (i=start;i<=8;i++) {
1851 context -> shift_count[i] = context -> shift_count[i+1] + 1;
1853 context -> shift_count[9] = context -> shift_count[9] + 1;
1858 /****************************************************************************
1861 * Set the console title
1863 void WCMD_title (WCHAR *command) {
1864 SetConsoleTitle(command);
1867 /****************************************************************************
1870 * Copy a file to standard output.
1873 void WCMD_type (WCHAR *command) {
1876 WCHAR *argN = command;
1877 BOOL writeHeaders = FALSE;
1879 if (param1[0] == 0x00) {
1880 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1884 if (param2[0] != 0x00) writeHeaders = TRUE;
1886 /* Loop through all args */
1889 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1897 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
1898 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
1899 FILE_ATTRIBUTE_NORMAL, NULL);
1900 if (h == INVALID_HANDLE_VALUE) {
1901 WCMD_print_error ();
1902 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
1906 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
1907 WCMD_output(fmt, thisArg);
1909 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL)) {
1910 if (count == 0) break; /* ReadFile reports success on EOF! */
1912 WCMD_output_asis (buffer);
1919 /****************************************************************************
1922 * Output either a file or stdin to screen in pages
1925 void WCMD_more (WCHAR *command) {
1928 WCHAR *argN = command;
1929 BOOL useinput = FALSE;
1931 WCHAR moreStrPage[100];
1934 static const WCHAR moreStart[] = {'-','-',' ','\0'};
1935 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
1936 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
1937 ')',' ','-','-','\n','\0'};
1938 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
1940 /* Prefix the NLS more with '-- ', then load the text */
1942 strcpyW(moreStr, moreStart);
1943 LoadString (hinst, WCMD_MORESTR, &moreStr[3],
1944 (sizeof(moreStr)/sizeof(WCHAR))-3);
1946 if (param1[0] == 0x00) {
1948 /* Wine implements pipes via temporary files, and hence stdin is
1949 effectively reading from the file. This means the prompts for
1950 more are satistied by the next line from the input (file). To
1951 avoid this, ensure stdin is to the console */
1952 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
1953 HANDLE hConIn = CreateFile(conInW, GENERIC_READ | GENERIC_WRITE,
1954 FILE_SHARE_READ, NULL, OPEN_EXISTING,
1955 FILE_ATTRIBUTE_NORMAL, 0);
1956 SetStdHandle(STD_INPUT_HANDLE, hConIn);
1958 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
1959 once you get in this bit unless due to a pipe, its going to end badly... */
1961 wsprintf(moreStrPage, moreFmt, moreStr);
1963 WCMD_enter_paged_mode(moreStrPage);
1964 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
1965 if (count == 0) break; /* ReadFile reports success on EOF! */
1967 WCMD_output_asis (buffer);
1969 WCMD_leave_paged_mode();
1971 /* Restore stdin to what it was */
1972 SetStdHandle(STD_INPUT_HANDLE, hstdin);
1973 CloseHandle(hConIn);
1977 BOOL needsPause = FALSE;
1979 /* Loop through all args */
1980 WCMD_enter_paged_mode(moreStrPage);
1983 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1991 wsprintf(moreStrPage, moreFmt2, moreStr, 100);
1992 WCMD_leave_paged_mode();
1993 WCMD_output_asis(moreStrPage);
1994 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
1995 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
1996 WCMD_enter_paged_mode(moreStrPage);
2000 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2001 h = CreateFile (thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2002 FILE_ATTRIBUTE_NORMAL, NULL);
2003 if (h == INVALID_HANDLE_VALUE) {
2004 WCMD_print_error ();
2005 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2009 ULONG64 fileLen = 0;
2010 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2012 /* Get the file size */
2013 GetFileAttributesEx(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2014 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2017 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2018 if (count == 0) break; /* ReadFile reports success on EOF! */
2022 /* Update % count (would be used in WCMD_output_asis as prompt) */
2023 wsprintf(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2025 WCMD_output_asis (buffer);
2031 WCMD_leave_paged_mode();
2035 /****************************************************************************
2038 * Display verify flag.
2039 * FIXME: We don't actually do anything with the verify flag other than toggle
2043 void WCMD_verify (WCHAR *command) {
2047 count = strlenW(command);
2049 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2050 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2053 if (lstrcmpiW(command, onW) == 0) {
2057 else if (lstrcmpiW(command, offW) == 0) {
2061 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2064 /****************************************************************************
2067 * Display version info.
2070 void WCMD_version (void) {
2072 WCMD_output (version_string);
2076 /****************************************************************************
2079 * Display volume info and/or set volume label. Returns 0 if error.
2082 int WCMD_volume (int mode, WCHAR *path) {
2084 DWORD count, serial;
2085 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2088 if (strlenW(path) == 0) {
2089 status = GetCurrentDirectory (sizeof(curdir)/sizeof(WCHAR), curdir);
2091 WCMD_print_error ();
2094 status = GetVolumeInformation (NULL, label, sizeof(label)/sizeof(WCHAR),
2095 &serial, NULL, NULL, NULL, 0);
2098 static const WCHAR fmt[] = {'%','s','\\','\0'};
2099 if ((path[1] != ':') || (strlenW(path) != 2)) {
2100 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2103 wsprintf (curdir, fmt, path);
2104 status = GetVolumeInformation (curdir, label, sizeof(label)/sizeof(WCHAR),
2109 WCMD_print_error ();
2112 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2113 curdir[0], label, HIWORD(serial), LOWORD(serial));
2115 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2116 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2117 sizeof(string)/sizeof(WCHAR), &count, NULL);
2119 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2120 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2122 if (strlenW(path) != 0) {
2123 if (!SetVolumeLabel (curdir, string)) WCMD_print_error ();
2126 if (!SetVolumeLabel (NULL, string)) WCMD_print_error ();
2132 /**************************************************************************
2135 * Exit either the process, or just this batch program
2139 void WCMD_exit (CMD_LIST **cmdList) {
2141 static const WCHAR parmB[] = {'/','B','\0'};
2142 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2144 if (context && lstrcmpiW(quals, parmB) == 0) {
2146 context -> skip_rest = TRUE;
2153 /**************************************************************************
2156 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
2159 * Returns True if Y (or A) answer is selected
2160 * If optionAll contains a pointer, ALL is allowed, and if answered
2164 BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
2166 WCHAR msgbuffer[MAXSTRING];
2167 WCHAR Ybuffer[MAXSTRING];
2168 WCHAR Nbuffer[MAXSTRING];
2169 WCHAR Abuffer[MAXSTRING];
2170 WCHAR answer[MAX_PATH] = {'\0'};
2173 /* Load the translated 'Are you sure', plus valid answers */
2174 LoadString (hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2175 LoadString (hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
2176 LoadString (hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
2177 LoadString (hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
2179 /* Loop waiting on a Y or N */
2180 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
2181 static const WCHAR startBkt[] = {' ','(','\0'};
2182 static const WCHAR endBkt[] = {')','?','\0'};
2184 WCMD_output_asis (message);
2186 WCMD_output_asis (msgbuffer);
2188 WCMD_output_asis (startBkt);
2189 WCMD_output_asis (Ybuffer);
2190 WCMD_output_asis (fslashW);
2191 WCMD_output_asis (Nbuffer);
2193 WCMD_output_asis (fslashW);
2194 WCMD_output_asis (Abuffer);
2196 WCMD_output_asis (endBkt);
2197 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
2198 sizeof(answer)/sizeof(WCHAR), &count, NULL);
2199 answer[0] = toupper(answer[0]);
2202 /* Return the answer */
2203 return ((answer[0] == Ybuffer[0]) ||
2204 (optionAll && (answer[0] == Abuffer[0])));
2207 /*****************************************************************************
2210 * Lists or sets file associations (assoc = TRUE)
2211 * Lists or sets file types (assoc = FALSE)
2213 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2216 DWORD accessOptions = KEY_READ;
2218 LONG rc = ERROR_SUCCESS;
2219 WCHAR keyValue[MAXSTRING];
2220 DWORD valueLen = MAXSTRING;
2222 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2223 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2225 /* See if parameter includes '=' */
2227 newValue = strchrW(command, '=');
2228 if (newValue) accessOptions |= KEY_WRITE;
2230 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2231 if (RegOpenKeyEx(HKEY_CLASSES_ROOT, nullW, 0,
2232 accessOptions, &key) != ERROR_SUCCESS) {
2233 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2237 /* If no parameters then list all associations */
2238 if (*command == 0x00) {
2241 /* Enumerate all the keys */
2242 while (rc != ERROR_NO_MORE_ITEMS) {
2243 WCHAR keyName[MAXSTRING];
2246 /* Find the next value */
2247 nameLen = MAXSTRING;
2248 rc = RegEnumKeyEx(key, index++,
2250 NULL, NULL, NULL, NULL);
2252 if (rc == ERROR_SUCCESS) {
2254 /* Only interested in extension ones if assoc, or others
2256 if ((keyName[0] == '.' && assoc) ||
2257 (!(keyName[0] == '.') && (!assoc)))
2259 WCHAR subkey[MAXSTRING];
2260 strcpyW(subkey, keyName);
2261 if (!assoc) strcatW(subkey, shOpCmdW);
2263 if (RegOpenKeyEx(key, subkey, 0,
2264 accessOptions, &readKey) == ERROR_SUCCESS) {
2266 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2267 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2268 (LPBYTE)keyValue, &valueLen);
2269 WCMD_output_asis(keyName);
2270 WCMD_output_asis(equalW);
2271 /* If no default value found, leave line empty after '=' */
2272 if (rc == ERROR_SUCCESS) {
2273 WCMD_output_asis(keyValue);
2275 WCMD_output_asis(newline);
2280 RegCloseKey(readKey);
2284 /* Parameter supplied - if no '=' on command line, its a query */
2285 if (newValue == NULL) {
2287 WCHAR subkey[MAXSTRING];
2289 /* Query terminates the parameter at the first space */
2290 strcpyW(keyValue, command);
2291 space = strchrW(keyValue, ' ');
2292 if (space) *space=0x00;
2294 /* Set up key name */
2295 strcpyW(subkey, keyValue);
2296 if (!assoc) strcatW(subkey, shOpCmdW);
2298 if (RegOpenKeyEx(key, subkey, 0,
2299 accessOptions, &readKey) == ERROR_SUCCESS) {
2301 rc = RegQueryValueEx(readKey, NULL, NULL, NULL,
2302 (LPBYTE)keyValue, &valueLen);
2303 WCMD_output_asis(command);
2304 WCMD_output_asis(equalW);
2305 /* If no default value found, leave line empty after '=' */
2306 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2307 WCMD_output_asis(newline);
2308 RegCloseKey(readKey);
2311 WCHAR msgbuffer[MAXSTRING];
2312 WCHAR outbuffer[MAXSTRING];
2314 /* Load the translated 'File association not found' */
2316 LoadString (hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2318 LoadString (hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2320 wsprintf(outbuffer, msgbuffer, keyValue);
2321 WCMD_output_asis(outbuffer);
2325 /* Not a query - its a set or clear of a value */
2328 WCHAR subkey[MAXSTRING];
2330 /* Get pointer to new value */
2334 /* Set up key name */
2335 strcpyW(subkey, command);
2336 if (!assoc) strcatW(subkey, shOpCmdW);
2338 /* If nothing after '=' then clear value - only valid for ASSOC */
2339 if (*newValue == 0x00) {
2341 if (assoc) rc = RegDeleteKey(key, command);
2342 if (assoc && rc == ERROR_SUCCESS) {
2343 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2345 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2350 WCHAR msgbuffer[MAXSTRING];
2351 WCHAR outbuffer[MAXSTRING];
2353 /* Load the translated 'File association not found' */
2355 LoadString (hinst, WCMD_NOASSOC, msgbuffer,
2356 sizeof(msgbuffer)/sizeof(WCHAR));
2358 LoadString (hinst, WCMD_NOFTYPE, msgbuffer,
2359 sizeof(msgbuffer)/sizeof(WCHAR));
2361 wsprintf(outbuffer, msgbuffer, keyValue);
2362 WCMD_output_asis(outbuffer);
2366 /* It really is a set value = contents */
2368 rc = RegCreateKeyEx(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2369 accessOptions, NULL, &readKey, NULL);
2370 if (rc == ERROR_SUCCESS) {
2371 rc = RegSetValueEx(readKey, NULL, 0, REG_SZ,
2372 (LPBYTE)newValue, strlenW(newValue));
2373 RegCloseKey(readKey);
2376 if (rc != ERROR_SUCCESS) {
2380 WCMD_output_asis(command);
2381 WCMD_output_asis(equalW);
2382 WCMD_output_asis(newValue);
2383 WCMD_output_asis(newline);
2393 /****************************************************************************
2396 * Clear the terminal screen.
2399 void WCMD_color (void) {
2401 /* Emulate by filling the screen from the top left to bottom right with
2402 spaces, then moving the cursor to the top left afterwards */
2403 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2404 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2406 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2407 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2411 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2417 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2422 /* Convert the color hex digits */
2423 if (param1[0] == 0x00) {
2424 color = defaultColor;
2426 color = strtoulW(param1, NULL, 16);
2429 /* Fail if fg == bg color */
2430 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2435 /* Set the current screen contents and ensure all future writes
2436 remain this color */
2437 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2438 SetConsoleTextAttribute(hStdOut, color);