2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * On entry to each function, global variables quals, param1, param2 contain
25 * the qualifiers (uppercased and concatenated) and parameters entered, with
26 * environment-variable and batch parameter substitution already done.
31 * - No support for pipes, shell parameters
32 * - Lots of functionality missing from builtins
33 * - Messages etc need international support
36 #define WIN32_LEAN_AND_MEAN
40 #include "wine/debug.h"
42 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
44 static void WCMD_part_execute(CMD_LIST **commands, const WCHAR *firstcmd,
45 const WCHAR *variable, const WCHAR *value,
46 BOOL isIF, BOOL conditionTRUE);
48 static struct env_stack *saved_environment;
49 struct env_stack *pushd_directories;
51 extern HINSTANCE hinst;
52 extern WCHAR inbuilt[][10];
53 extern int verify_mode, defaultColor;
54 extern BOOL echo_mode;
55 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
56 extern BATCH_CONTEXT *context;
57 extern DWORD errorlevel;
59 static const WCHAR dotW[] = {'.','\0'};
60 static const WCHAR dotdotW[] = {'.','.','\0'};
61 static const WCHAR slashW[] = {'\\','\0'};
62 static const WCHAR starW[] = {'*','\0'};
63 static const WCHAR equalW[] = {'=','\0'};
64 static const WCHAR fslashW[] = {'/','\0'};
65 static const WCHAR onW[] = {'O','N','\0'};
66 static const WCHAR offW[] = {'O','F','F','\0'};
67 static const WCHAR parmY[] = {'/','Y','\0'};
68 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
69 static const WCHAR nullW[] = {'\0'};
71 /**************************************************************************
74 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
77 * Returns True if Y (or A) answer is selected
78 * If optionAll contains a pointer, ALL is allowed, and if answered
82 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
83 const BOOL *optionAll) {
85 WCHAR msgbuffer[MAXSTRING];
86 WCHAR Ybuffer[MAXSTRING];
87 WCHAR Nbuffer[MAXSTRING];
88 WCHAR Abuffer[MAXSTRING];
89 WCHAR answer[MAX_PATH] = {'\0'};
92 /* Load the translated 'Are you sure', plus valid answers */
93 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
94 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
95 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
96 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
98 /* Loop waiting on a Y or N */
99 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
100 static const WCHAR startBkt[] = {' ','(','\0'};
101 static const WCHAR endBkt[] = {')','?','\0'};
103 WCMD_output_asis (message);
105 WCMD_output_asis (msgbuffer);
107 WCMD_output_asis (startBkt);
108 WCMD_output_asis (Ybuffer);
109 WCMD_output_asis (fslashW);
110 WCMD_output_asis (Nbuffer);
112 WCMD_output_asis (fslashW);
113 WCMD_output_asis (Abuffer);
115 WCMD_output_asis (endBkt);
116 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
117 sizeof(answer)/sizeof(WCHAR), &count, NULL);
118 answer[0] = toupperW(answer[0]);
121 /* Return the answer */
122 return ((answer[0] == Ybuffer[0]) ||
123 (optionAll && (answer[0] == Abuffer[0])));
126 /****************************************************************************
129 * Clear the terminal screen.
132 void WCMD_clear_screen (void) {
134 /* Emulate by filling the screen from the top left to bottom right with
135 spaces, then moving the cursor to the top left afterwards */
136 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
137 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
139 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
144 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
148 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
149 SetConsoleCursorPosition(hStdOut, topLeft);
153 /****************************************************************************
156 * Change the default i/o device (ie redirect STDin/STDout).
159 void WCMD_change_tty (void) {
161 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
165 /****************************************************************************
170 void WCMD_choice (const WCHAR * command) {
172 static const WCHAR bellW[] = {7,0};
173 static const WCHAR commaW[] = {',',0};
174 static const WCHAR bracket_open[] = {'[',0};
175 static const WCHAR bracket_close[] = {']','?',0};
180 WCHAR *my_command = NULL;
181 WCHAR opt_default = 0;
182 DWORD opt_timeout = 0;
189 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
192 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
196 ptr = WCMD_skip_leading_spaces(my_command);
197 while (*ptr == '/') {
198 switch (toupperW(ptr[1])) {
201 /* the colon is optional */
205 if (!*ptr || isspaceW(*ptr)) {
206 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
207 HeapFree(GetProcessHeap(), 0, my_command);
211 /* remember the allowed keys (overwrite previous /C option) */
213 while (*ptr && (!isspaceW(*ptr)))
217 /* terminate allowed chars */
219 ptr = WCMD_skip_leading_spaces(&ptr[1]);
221 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
226 ptr = WCMD_skip_leading_spaces(&ptr[2]);
231 ptr = WCMD_skip_leading_spaces(&ptr[2]);
236 /* the colon is optional */
240 opt_default = *ptr++;
242 if (!opt_default || (*ptr != ',')) {
243 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
244 HeapFree(GetProcessHeap(), 0, my_command);
250 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
256 opt_timeout = atoiW(answer);
258 ptr = WCMD_skip_leading_spaces(ptr);
262 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
263 HeapFree(GetProcessHeap(), 0, my_command);
269 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
272 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
274 /* use default keys, when needed: localized versions of "Y"es and "No" */
276 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
277 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
282 /* print the question, when needed */
284 WCMD_output_asis(ptr);
288 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
292 /* print a list of all allowed answers inside brackets */
293 WCMD_output_asis(bracket_open);
296 while ((answer[0] = *ptr++)) {
297 WCMD_output_asis(answer);
299 WCMD_output_asis(commaW);
301 WCMD_output_asis(bracket_close);
306 /* FIXME: Add support for option /T */
307 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count, NULL);
310 answer[0] = toupperW(answer[0]);
312 ptr = strchrW(opt_c, answer[0]);
314 WCMD_output_asis(answer);
315 WCMD_output(newline);
317 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
319 errorlevel = (ptr - opt_c) + 1;
320 WINE_TRACE("answer: %d\n", errorlevel);
321 HeapFree(GetProcessHeap(), 0, my_command);
326 /* key not allowed: play the bell */
327 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
328 WCMD_output_asis(bellW);
333 /****************************************************************************
336 * Copy a file or wildcarded set.
337 * FIXME: Add support for a+b+c type syntax
340 void WCMD_copy (void) {
345 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
347 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
348 BOOL copyToDir = FALSE;
349 WCHAR srcspec[MAX_PATH];
353 WCHAR fname[MAX_PATH];
356 if (param1[0] == 0x00) {
357 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
361 /* Convert source into full spec */
362 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
363 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
364 if (srcpath[strlenW(srcpath) - 1] == '\\')
365 srcpath[strlenW(srcpath) - 1] = '\0';
367 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
368 attribs = GetFileAttributesW(srcpath);
372 strcpyW(srcspec, srcpath);
374 /* If a directory, then add \* on the end when searching */
375 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
376 strcatW(srcpath, slashW);
377 strcatW(srcspec, slashW);
378 strcatW(srcspec, starW);
380 WCMD_splitpath(srcpath, drive, dir, fname, ext);
381 strcpyW(srcpath, drive);
382 strcatW(srcpath, dir);
385 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
387 /* If no destination supplied, assume current directory */
388 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
389 if (param2[0] == 0x00) {
390 strcpyW(param2, dotW);
393 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
394 if (outpath[strlenW(outpath) - 1] == '\\')
395 outpath[strlenW(outpath) - 1] = '\0';
396 attribs = GetFileAttributesW(outpath);
397 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
398 strcatW (outpath, slashW);
401 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
402 wine_dbgstr_w(outpath), copyToDir);
404 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
405 if (strstrW (quals, parmNoY))
407 else if (strstrW (quals, parmY))
410 /* By default, we will force the overwrite in batch mode and ask for
411 * confirmation in interactive mode. */
414 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
415 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
416 * default behavior. */
417 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
418 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
419 if (!lstrcmpiW (copycmd, parmY))
421 else if (!lstrcmpiW (copycmd, parmNoY))
426 /* Loop through all source files */
427 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
428 hff = FindFirstFileW(srcspec, &fd);
429 if (hff != INVALID_HANDLE_VALUE) {
431 WCHAR outname[MAX_PATH];
432 WCHAR srcname[MAX_PATH];
433 BOOL overwrite = force;
435 /* Destination is either supplied filename, or source name in
436 supplied destination directory */
437 strcpyW(outname, outpath);
438 if (copyToDir) strcatW(outname, fd.cFileName);
439 strcpyW(srcname, srcpath);
440 strcatW(srcname, fd.cFileName);
442 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
443 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
445 /* Skip . and .., and directories */
446 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
448 WINE_TRACE("Skipping directories\n");
451 /* Prompt before overwriting */
452 else if (!overwrite) {
453 attribs = GetFileAttributesW(outname);
454 if (attribs != INVALID_FILE_ATTRIBUTES) {
455 WCHAR buffer[MAXSTRING];
456 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
457 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
459 else overwrite = TRUE;
462 /* Do the copy as appropriate */
464 status = CopyFileW(srcname, outname, FALSE);
465 if (!status) WCMD_print_error ();
468 } while (FindNextFileW(hff, &fd) != 0);
471 status = ERROR_FILE_NOT_FOUND;
476 /****************************************************************************
479 * Create a directory (and, if needed, any intermediate directories).
481 * Modifies its argument by replacing slashes temporarily with nulls.
484 static BOOL create_full_path(WCHAR* path)
488 /* don't mess with drive letter portion of path, if any */
493 /* Strip trailing slashes. */
494 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
497 /* Step through path, creating intermediate directories as needed. */
498 /* First component includes drive letter, if any. */
502 /* Skip to end of component */
503 while (*p == '\\') p++;
504 while (*p && *p != '\\') p++;
506 /* path is now the original full path */
507 return CreateDirectoryW(path, NULL);
509 /* Truncate path, create intermediate directory, and restore path */
511 rv = CreateDirectoryW(path, NULL);
513 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
520 void WCMD_create_dir (WCHAR *command) {
522 WCHAR *argN = command;
524 if (param1[0] == 0x00) {
525 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
528 /* Loop through all args */
530 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL);
532 if (!create_full_path(thisArg)) {
539 /* Parse the /A options given by the user on the commandline
540 * into a bitmask of wanted attributes (*wantSet),
541 * and a bitmask of unwanted attributes (*wantClear).
543 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
544 static const WCHAR parmA[] = {'/','A','\0'};
547 /* both are strictly 'out' parameters */
551 /* For each /A argument */
552 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
556 /* Skip optional : */
559 /* For each of the attribute specifier chars to this /A option */
560 for (; *p != 0 && *p != '/'; p++) {
569 /* Convert the attribute specifier to a bit in one of the masks */
571 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
572 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
573 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
574 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
576 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
586 /* If filename part of parameter is * or *.*,
587 * and neither /Q nor /P options were given,
588 * prompt the user whether to proceed.
589 * Returns FALSE if user says no, TRUE otherwise.
590 * *pPrompted is set to TRUE if the user is prompted.
591 * (If /P supplied, del will prompt for individual files later.)
593 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
594 static const WCHAR parmP[] = {'/','P','\0'};
595 static const WCHAR parmQ[] = {'/','Q','\0'};
597 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
598 static const WCHAR anyExt[]= {'.','*','\0'};
601 WCHAR fname[MAX_PATH];
603 WCHAR fpath[MAX_PATH];
605 /* Convert path into actual directory spec */
606 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
607 WCMD_splitpath(fpath, drive, dir, fname, ext);
609 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
610 if ((strcmpW(fname, starW) == 0) &&
611 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
613 WCHAR question[MAXSTRING];
614 static const WCHAR fmt[] = {'%','s',' ','\0'};
616 /* Caller uses this to suppress "file not found" warning later */
619 /* Ask for confirmation */
620 wsprintfW(question, fmt, fpath);
621 return WCMD_ask_confirm(question, TRUE, NULL);
624 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
628 /* Helper function for WCMD_delete().
629 * Deletes a single file, directory, or wildcard.
630 * If /S was given, does it recursively.
631 * Returns TRUE if a file was deleted.
633 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
635 static const WCHAR parmP[] = {'/','P','\0'};
636 static const WCHAR parmS[] = {'/','S','\0'};
637 static const WCHAR parmF[] = {'/','F','\0'};
639 DWORD unwanted_attrs;
641 WCHAR argCopy[MAX_PATH];
644 WCHAR fpath[MAX_PATH];
646 BOOL handleParm = TRUE;
648 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
650 strcpyW(argCopy, thisArg);
651 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
652 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
654 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
655 /* Skip this arg if user declines to delete *.* */
659 /* First, try to delete in the current directory */
660 hff = FindFirstFileW(argCopy, &fd);
661 if (hff == INVALID_HANDLE_VALUE) {
667 /* Support del <dirname> by just deleting all files dirname\* */
669 && (strchrW(argCopy,'*') == NULL)
670 && (strchrW(argCopy,'?') == NULL)
671 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
673 WCHAR modifiedParm[MAX_PATH];
674 static const WCHAR slashStar[] = {'\\','*','\0'};
676 strcpyW(modifiedParm, argCopy);
677 strcatW(modifiedParm, slashStar);
680 WCMD_delete_one(modifiedParm);
682 } else if (handleParm) {
684 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
685 strcpyW (fpath, argCopy);
687 p = strrchrW (fpath, '\\');
690 strcatW (fpath, fd.cFileName);
692 else strcpyW (fpath, fd.cFileName);
693 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
696 /* Handle attribute matching (/A) */
697 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
698 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
700 /* /P means prompt for each file */
701 if (ok && strstrW (quals, parmP) != NULL) {
702 WCHAR question[MAXSTRING];
704 /* Ask for confirmation */
705 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
706 ok = WCMD_ask_confirm(question, FALSE, NULL);
709 /* Only proceed if ok to */
712 /* If file is read only, and /A:r or /F supplied, delete it */
713 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
714 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
715 strstrW (quals, parmF) != NULL)) {
716 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
719 /* Now do the delete */
720 if (!DeleteFileW(fpath)) WCMD_print_error ();
724 } while (FindNextFileW(hff, &fd) != 0);
728 /* Now recurse into all subdirectories handling the parameter in the same way */
729 if (strstrW (quals, parmS) != NULL) {
731 WCHAR thisDir[MAX_PATH];
736 WCHAR fname[MAX_PATH];
739 /* Convert path into actual directory spec */
740 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
741 WCMD_splitpath(thisDir, drive, dir, fname, ext);
743 strcpyW(thisDir, drive);
744 strcatW(thisDir, dir);
745 cPos = strlenW(thisDir);
747 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
749 /* Append '*' to the directory */
751 thisDir[cPos+1] = 0x00;
753 hff = FindFirstFileW(thisDir, &fd);
755 /* Remove residual '*' */
756 thisDir[cPos] = 0x00;
758 if (hff != INVALID_HANDLE_VALUE) {
759 DIRECTORY_STACK *allDirs = NULL;
760 DIRECTORY_STACK *lastEntry = NULL;
763 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
764 (strcmpW(fd.cFileName, dotdotW) != 0) &&
765 (strcmpW(fd.cFileName, dotW) != 0)) {
767 DIRECTORY_STACK *nextDir;
768 WCHAR subParm[MAX_PATH];
770 /* Work out search parameter in sub dir */
771 strcpyW (subParm, thisDir);
772 strcatW (subParm, fd.cFileName);
773 strcatW (subParm, slashW);
774 strcatW (subParm, fname);
775 strcatW (subParm, ext);
776 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
778 /* Allocate memory, add to list */
779 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
780 if (allDirs == NULL) allDirs = nextDir;
781 if (lastEntry != NULL) lastEntry->next = nextDir;
783 nextDir->next = NULL;
784 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
785 (strlenW(subParm)+1) * sizeof(WCHAR));
786 strcpyW(nextDir->dirName, subParm);
788 } while (FindNextFileW(hff, &fd) != 0);
791 /* Go through each subdir doing the delete */
792 while (allDirs != NULL) {
793 DIRECTORY_STACK *tempDir;
795 tempDir = allDirs->next;
796 found |= WCMD_delete_one (allDirs->dirName);
798 HeapFree(GetProcessHeap(),0,allDirs->dirName);
799 HeapFree(GetProcessHeap(),0,allDirs);
808 /****************************************************************************
811 * Delete a file or wildcarded set.
814 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
815 * - Each set is a pattern, eg /ahr /as-r means
816 * readonly+hidden OR nonreadonly system files
817 * - The '-' applies to a single field, ie /a:-hr means read only
821 BOOL WCMD_delete (WCHAR *command) {
824 BOOL argsProcessed = FALSE;
825 BOOL foundAny = FALSE;
829 for (argno=0; ; argno++) {
834 thisArg = WCMD_parameter (command, argno, &argN, NULL);
836 break; /* no more parameters */
838 continue; /* skip options */
840 argsProcessed = TRUE;
841 found = WCMD_delete_one(thisArg);
844 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
849 /* Handle no valid args */
851 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
856 /****************************************************************************
859 * Echo input to the screen (or not). We don't try to emulate the bugs
860 * in DOS (try typing "ECHO ON AGAIN" for an example).
863 void WCMD_echo (const WCHAR *command) {
866 const WCHAR *origcommand = command;
868 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
869 || command[0]==':' || command[0]==';')
872 count = strlenW(command);
873 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
874 && origcommand[0]!=';') {
875 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
876 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
879 if (lstrcmpiW(command, onW) == 0) {
883 if (lstrcmpiW(command, offW) == 0) {
887 WCMD_output_asis (command);
888 WCMD_output (newline);
892 /**************************************************************************
895 * Batch file loop processing.
897 * On entry: cmdList contains the syntax up to the set
898 * next cmdList and all in that bracket contain the set data
899 * next cmdlist contains the DO cmd
900 * following that is either brackets or && entries (as per if)
904 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
909 static const WCHAR inW[] = {'i','n'};
910 static const WCHAR doW[] = {'d','o'};
911 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
917 BOOL expandDirs = FALSE;
918 BOOL useNumbers = FALSE;
919 BOOL doFileset = FALSE;
920 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
922 CMD_LIST *thisCmdStart;
925 /* Handle optional qualifiers (multiple are allowed) */
926 while (*curPos && *curPos == '/') {
927 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
929 switch (toupperW(*curPos)) {
930 case 'D': curPos++; expandDirs = TRUE; break;
931 case 'L': curPos++; useNumbers = TRUE; break;
933 /* Recursive is special case - /R can have an optional path following it */
934 /* filenamesets are another special case - /F can have an optional options following it */
938 BOOL isRecursive = (*curPos == 'R');
943 /* Skip whitespace */
945 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
947 /* Next parm is either qualifier, path/options or variable -
948 only care about it if it is the path/options */
949 if (*curPos && *curPos != '/' && *curPos != '%') {
950 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
952 static unsigned int once;
953 if (!once++) WINE_FIXME("/F needs to handle options\n");
959 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
963 /* Skip whitespace between qualifiers */
964 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
967 /* Skip whitespace before variable */
968 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
970 /* Ensure line continues with variable */
971 if (!*curPos || *curPos != '%') {
972 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
976 /* Variable should follow */
978 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
979 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
981 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
984 /* Skip whitespace before IN */
985 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
987 /* Ensure line continues with IN */
989 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
991 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
995 /* Save away where the set of data starts and the variable */
996 thisDepth = (*cmdList)->bracketDepth;
997 *cmdList = (*cmdList)->nextcommand;
998 setStart = (*cmdList);
1000 /* Skip until the close bracket */
1001 WINE_TRACE("Searching %p as the set\n", *cmdList);
1003 (*cmdList)->command != NULL &&
1004 (*cmdList)->bracketDepth > thisDepth) {
1005 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1006 *cmdList = (*cmdList)->nextcommand;
1009 /* Skip the close bracket, if there is one */
1010 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1012 /* Syntax error if missing close bracket, or nothing following it
1013 and once we have the complete set, we expect a DO */
1014 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1015 if ((*cmdList == NULL)
1016 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1018 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1022 /* Save away the starting position for the commands (and offset for the
1024 cmdStart = *cmdList;
1026 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1030 /* Loop through all set entries */
1032 thisSet->command != NULL &&
1033 thisSet->bracketDepth >= thisDepth) {
1035 /* Loop through all entries on the same line */
1039 WINE_TRACE("Processing for set %p\n", thisSet);
1041 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1044 * If the parameter within the set has a wildcard then search for matching files
1045 * otherwise do a literal substitution.
1047 static const WCHAR wildcards[] = {'*','?','\0'};
1048 thisCmdStart = cmdStart;
1051 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1053 if (!useNumbers && !doFileset) {
1054 if (strpbrkW (item, wildcards)) {
1055 hff = FindFirstFileW(item, &fd);
1056 if (hff != INVALID_HANDLE_VALUE) {
1058 BOOL isDirectory = FALSE;
1060 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1062 /* Handle as files or dirs appropriately, but ignore . and .. */
1063 if (isDirectory == expandDirs &&
1064 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1065 (strcmpW(fd.cFileName, dotW) != 0))
1067 thisCmdStart = cmdStart;
1068 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1069 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1070 fd.cFileName, FALSE, TRUE);
1073 } while (FindNextFileW(hff, &fd) != 0);
1077 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1080 } else if (useNumbers) {
1081 /* Convert the first 3 numbers to signed longs and save */
1082 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1083 /* else ignore them! */
1085 /* Filesets - either a list of files, or a command to run and parse the output */
1086 } else if (doFileset && *itemStart != '"') {
1089 WCHAR temp_file[MAX_PATH];
1091 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1092 wine_dbgstr_w(item));
1094 /* If backquote or single quote, we need to launch that command
1095 and parse the results - use a temporary file */
1096 if (*itemStart == '`' || *itemStart == '\'') {
1098 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1099 static const WCHAR redirOut[] = {'>','%','s','\0'};
1100 static const WCHAR cmdW[] = {'C','M','D','\0'};
1102 /* Remove trailing character */
1103 itemStart[strlenW(itemStart)-1] = 0x00;
1105 /* Get temp filename */
1106 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1107 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1109 /* Execute program and redirect output */
1110 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1111 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1113 /* Open the file, read line by line and process */
1114 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1115 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1118 /* Open the file, read line by line and process */
1119 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1120 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1123 /* Process the input file */
1124 if (input == INVALID_HANDLE_VALUE) {
1125 WCMD_print_error ();
1126 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1128 return; /* FOR loop aborts at first failure here */
1132 WCHAR buffer[MAXSTRING] = {'\0'};
1133 WCHAR *where, *parm;
1135 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1137 /* Skip blank lines*/
1138 parm = WCMD_parameter (buffer, 0, &where, NULL);
1139 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1140 wine_dbgstr_w(buffer));
1143 /* FIXME: The following should be moved into its own routine and
1144 reused for the string literal parsing below */
1145 thisCmdStart = cmdStart;
1146 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1147 cmdEnd = thisCmdStart;
1153 CloseHandle (input);
1156 /* Delete the temporary file */
1157 if (*itemStart == '`' || *itemStart == '\'') {
1158 DeleteFileW(temp_file);
1161 /* Filesets - A string literal */
1162 } else if (doFileset && *itemStart == '"') {
1163 WCHAR buffer[MAXSTRING] = {'\0'};
1164 WCHAR *where, *parm;
1166 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1167 strcpyW(buffer, item);
1168 parm = WCMD_parameter (buffer, 0, &where, NULL);
1169 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1170 wine_dbgstr_w(buffer));
1173 /* FIXME: The following should be moved into its own routine and
1174 reused for the string literal parsing below */
1175 thisCmdStart = cmdStart;
1176 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1177 cmdEnd = thisCmdStart;
1181 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1182 cmdEnd = thisCmdStart;
1186 /* Move onto the next set line */
1187 thisSet = thisSet->nextcommand;
1190 /* If /L is provided, now run the for loop */
1193 static const WCHAR fmt[] = {'%','d','\0'};
1195 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1196 numbers[0], numbers[2], numbers[1]);
1198 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1201 sprintfW(thisNum, fmt, i);
1202 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1204 thisCmdStart = cmdStart;
1205 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1206 cmdEnd = thisCmdStart;
1210 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1211 all processing, OR it should be pointing to the end of && processing OR
1212 it should be pointing at the NULL end of bracket for the DO. The return
1213 value needs to be the NEXT command to execute, which it either is, or
1214 we need to step over the closing bracket */
1216 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1220 /*****************************************************************************
1223 * Execute a command, and any && or bracketed follow on to the command. The
1224 * first command to be executed may not be at the front of the
1225 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1227 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1228 const WCHAR *variable, const WCHAR *value,
1229 BOOL isIF, BOOL conditionTRUE) {
1231 CMD_LIST *curPosition = *cmdList;
1232 int myDepth = (*cmdList)->bracketDepth;
1234 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1235 cmdList, wine_dbgstr_w(firstcmd),
1236 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1239 /* Skip leading whitespace between condition and the command */
1240 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1242 /* Process the first command, if there is one */
1243 if (conditionTRUE && firstcmd && *firstcmd) {
1244 WCHAR *command = WCMD_strdupW(firstcmd);
1245 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1246 HeapFree(GetProcessHeap(), 0, command);
1250 /* If it didn't move the position, step to next command */
1251 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1253 /* Process any other parts of the command */
1255 BOOL processThese = TRUE;
1257 if (isIF) processThese = conditionTRUE;
1260 static const WCHAR ifElse[] = {'e','l','s','e'};
1262 /* execute all appropriate commands */
1263 curPosition = *cmdList;
1265 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1267 (*cmdList)->prevDelim,
1268 (*cmdList)->bracketDepth, myDepth);
1270 /* Execute any statements appended to the line */
1271 /* FIXME: Only if previous call worked for && or failed for || */
1272 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1273 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1274 if (processThese && (*cmdList)->command) {
1275 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1278 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1280 /* Execute any appended to the statement with (...) */
1281 } else if ((*cmdList)->bracketDepth > myDepth) {
1283 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1284 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1286 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1288 /* End of the command - does 'ELSE ' follow as the next command? */
1291 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1292 (*cmdList)->command)) {
1294 /* Swap between if and else processing */
1295 processThese = !processThese;
1297 /* Process the ELSE part */
1299 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1300 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1302 /* Skip leading whitespace between condition and the command */
1303 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1305 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1308 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1310 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1319 /**************************************************************************
1322 * Simple on-line help. Help text is stored in the resource file.
1325 void WCMD_give_help (const WCHAR *command) {
1329 command = WCMD_skip_leading_spaces((WCHAR*) command);
1330 if (strlenW(command) == 0) {
1331 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1334 /* Display help message for builtin commands */
1335 for (i=0; i<=WCMD_EXIT; i++) {
1336 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1337 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1338 WCMD_output_asis (WCMD_LoadMessage(i));
1342 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1343 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1344 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1345 command, -1, externals[i], -1) == CSTR_EQUAL) {
1347 static const WCHAR helpW[] = {' ', '/','?','\0'};
1348 strcpyW(cmd, command);
1349 strcatW(cmd, helpW);
1350 WCMD_run_program(cmd, 0);
1354 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1359 /****************************************************************************
1362 * Batch file jump instruction. Not the most efficient algorithm ;-)
1363 * Prints error message if the specified label cannot be found - the file pointer is
1364 * then at EOF, effectively stopping the batch file.
1365 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1368 void WCMD_goto (CMD_LIST **cmdList) {
1370 WCHAR string[MAX_PATH];
1371 WCHAR current[MAX_PATH];
1373 /* Do not process any more parts of a processed multipart or multilines command */
1374 if (cmdList) *cmdList = NULL;
1376 if (context != NULL) {
1377 WCHAR *paramStart = param1, *str;
1378 static const WCHAR eofW[] = {':','e','o','f','\0'};
1380 if (param1[0] == 0x00) {
1381 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1385 /* Handle special :EOF label */
1386 if (lstrcmpiW (eofW, param1) == 0) {
1387 context -> skip_rest = TRUE;
1391 /* Support goto :label as well as goto label */
1392 if (*paramStart == ':') paramStart++;
1394 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1395 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1397 while (isspaceW (*str)) str++;
1401 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1404 /* ignore space at the end */
1406 if (lstrcmpiW (current, paramStart) == 0) return;
1409 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1414 /*****************************************************************************
1417 * Push a directory onto the stack
1420 void WCMD_pushd (WCHAR *command) {
1421 struct env_stack *curdir;
1423 static const WCHAR parmD[] = {'/','D','\0'};
1425 if (strchrW(command, '/') != NULL) {
1426 SetLastError(ERROR_INVALID_PARAMETER);
1431 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1432 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1433 if( !curdir || !thisdir ) {
1436 WINE_ERR ("out of memory\n");
1440 /* Change directory using CD code with /D parameter */
1441 strcpyW(quals, parmD);
1442 GetCurrentDirectoryW (1024, thisdir);
1444 WCMD_setshow_default(command);
1450 curdir -> next = pushd_directories;
1451 curdir -> strings = thisdir;
1452 if (pushd_directories == NULL) {
1453 curdir -> u.stackdepth = 1;
1455 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1457 pushd_directories = curdir;
1462 /*****************************************************************************
1465 * Pop a directory from the stack
1468 void WCMD_popd (void) {
1469 struct env_stack *temp = pushd_directories;
1471 if (!pushd_directories)
1474 /* pop the old environment from the stack, and make it the current dir */
1475 pushd_directories = temp->next;
1476 SetCurrentDirectoryW(temp->strings);
1477 LocalFree (temp->strings);
1481 /****************************************************************************
1484 * Batch file conditional.
1486 * On entry, cmdlist will point to command containing the IF, and optionally
1487 * the first command to execute (if brackets not found)
1488 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1489 * If ('s were found, execute all within that bracket
1490 * Command may optionally be followed by an ELSE - need to skip instructions
1491 * in the else using the same logic
1493 * FIXME: Much more syntax checking needed!
1496 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1498 int negate; /* Negate condition */
1499 int test; /* Condition evaluation result */
1500 WCHAR condition[MAX_PATH], *command, *s;
1501 static const WCHAR notW[] = {'n','o','t','\0'};
1502 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1503 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1504 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1505 static const WCHAR eqeqW[] = {'=','=','\0'};
1506 static const WCHAR parmI[] = {'/','I','\0'};
1507 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1509 negate = !lstrcmpiW(param1,notW);
1510 strcpyW(condition, (negate ? param2 : param1));
1511 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1513 if (!lstrcmpiW (condition, errlvlW)) {
1514 test = (errorlevel >= atoiW(WCMD_parameter(p, 1+negate, NULL, NULL)));
1515 WCMD_parameter(p, 2+negate, &command, NULL);
1517 else if (!lstrcmpiW (condition, existW)) {
1518 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1519 WCMD_parameter(p, 2+negate, &command, NULL);
1521 else if (!lstrcmpiW (condition, defdW)) {
1522 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1523 WCMD_parameter(p, 2+negate, &command, NULL);
1525 else if ((s = strstrW (p, eqeqW))) {
1526 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1527 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1529 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1530 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1531 test = caseInsensitive
1532 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1533 leftPart, leftPartEnd-leftPart+1,
1534 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1535 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1536 leftPart, leftPartEnd-leftPart+1,
1537 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1538 WCMD_parameter(s, 1, &command, NULL);
1541 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1545 /* Process rest of IF statement which is on the same line
1546 Note: This may process all or some of the cmdList (eg a GOTO) */
1547 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1550 /****************************************************************************
1553 * Move a file, directory tree or wildcarded set of files.
1556 void WCMD_move (void) {
1559 WIN32_FIND_DATAW fd;
1561 WCHAR input[MAX_PATH];
1562 WCHAR output[MAX_PATH];
1564 WCHAR dir[MAX_PATH];
1565 WCHAR fname[MAX_PATH];
1566 WCHAR ext[MAX_PATH];
1568 if (param1[0] == 0x00) {
1569 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1573 /* If no destination supplied, assume current directory */
1574 if (param2[0] == 0x00) {
1575 strcpyW(param2, dotW);
1578 /* If 2nd parm is directory, then use original filename */
1579 /* Convert partial path to full path */
1580 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1581 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1582 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1583 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1585 /* Split into components */
1586 WCMD_splitpath(input, drive, dir, fname, ext);
1588 hff = FindFirstFileW(input, &fd);
1589 while (hff != INVALID_HANDLE_VALUE) {
1590 WCHAR dest[MAX_PATH];
1591 WCHAR src[MAX_PATH];
1594 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1596 /* Build src & dest name */
1597 strcpyW(src, drive);
1600 /* See if dest is an existing directory */
1601 attribs = GetFileAttributesW(output);
1602 if (attribs != INVALID_FILE_ATTRIBUTES &&
1603 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1604 strcpyW(dest, output);
1605 strcatW(dest, slashW);
1606 strcatW(dest, fd.cFileName);
1608 strcpyW(dest, output);
1611 strcatW(src, fd.cFileName);
1613 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1614 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1616 /* Check if file is read only, otherwise move it */
1617 attribs = GetFileAttributesW(src);
1618 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1619 (attribs & FILE_ATTRIBUTE_READONLY)) {
1620 SetLastError(ERROR_ACCESS_DENIED);
1625 /* If destination exists, prompt unless /Y supplied */
1626 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1628 WCHAR copycmd[MAXSTRING];
1631 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1632 if (strstrW (quals, parmNoY))
1634 else if (strstrW (quals, parmY))
1637 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1638 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1639 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1640 && ! lstrcmpiW (copycmd, parmY));
1643 /* Prompt if overwriting */
1645 WCHAR question[MAXSTRING];
1648 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1650 /* Ask for confirmation */
1651 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1652 ok = WCMD_ask_confirm(question, FALSE, NULL);
1654 /* So delete the destination prior to the move */
1656 if (!DeleteFileW(dest)) {
1657 WCMD_print_error ();
1666 status = MoveFileW(src, dest);
1668 status = 1; /* Anything other than 0 to prevent error msg below */
1673 WCMD_print_error ();
1677 /* Step on to next match */
1678 if (FindNextFileW(hff, &fd) == 0) {
1680 hff = INVALID_HANDLE_VALUE;
1686 /****************************************************************************
1689 * Wait for keyboard input.
1692 void WCMD_pause (void) {
1697 WCMD_output (anykey);
1698 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1699 sizeof(string)/sizeof(WCHAR), &count, NULL);
1702 /****************************************************************************
1705 * Delete a directory.
1708 void WCMD_remove_dir (WCHAR *command) {
1711 int argsProcessed = 0;
1712 WCHAR *argN = command;
1713 static const WCHAR parmS[] = {'/','S','\0'};
1714 static const WCHAR parmQ[] = {'/','Q','\0'};
1716 /* Loop through all args */
1718 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1719 if (argN && argN[0] != '/') {
1720 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1721 wine_dbgstr_w(quals));
1724 /* If subdirectory search not supplied, just try to remove
1725 and report error if it fails (eg if it contains a file) */
1726 if (strstrW (quals, parmS) == NULL) {
1727 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1729 /* Otherwise use ShFileOp to recursively remove a directory */
1732 SHFILEOPSTRUCTW lpDir;
1735 if (strstrW (quals, parmQ) == NULL) {
1737 WCHAR question[MAXSTRING];
1738 static const WCHAR fmt[] = {'%','s',' ','\0'};
1740 /* Ask for confirmation */
1741 wsprintfW(question, fmt, thisArg);
1742 ok = WCMD_ask_confirm(question, TRUE, NULL);
1744 /* Abort if answer is 'N' */
1751 lpDir.pFrom = thisArg;
1752 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1753 lpDir.wFunc = FO_DELETE;
1754 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1759 /* Handle no valid args */
1760 if (argsProcessed == 0) {
1761 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1767 /****************************************************************************
1773 void WCMD_rename (void) {
1777 WIN32_FIND_DATAW fd;
1778 WCHAR input[MAX_PATH];
1779 WCHAR *dotDst = NULL;
1781 WCHAR dir[MAX_PATH];
1782 WCHAR fname[MAX_PATH];
1783 WCHAR ext[MAX_PATH];
1788 /* Must be at least two args */
1789 if (param1[0] == 0x00 || param2[0] == 0x00) {
1790 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1795 /* Destination cannot contain a drive letter or directory separator */
1796 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1797 SetLastError(ERROR_INVALID_PARAMETER);
1803 /* Convert partial path to full path */
1804 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1805 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1806 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1807 dotDst = strchrW(param2, '.');
1809 /* Split into components */
1810 WCMD_splitpath(input, drive, dir, fname, ext);
1812 hff = FindFirstFileW(input, &fd);
1813 while (hff != INVALID_HANDLE_VALUE) {
1814 WCHAR dest[MAX_PATH];
1815 WCHAR src[MAX_PATH];
1816 WCHAR *dotSrc = NULL;
1819 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1821 /* FIXME: If dest name or extension is *, replace with filename/ext
1822 part otherwise use supplied name. This supports:
1824 ren jim.* fred.* etc
1825 However, windows has a more complex algorithm supporting eg
1826 ?'s and *'s mid name */
1827 dotSrc = strchrW(fd.cFileName, '.');
1829 /* Build src & dest name */
1830 strcpyW(src, drive);
1833 dirLen = strlenW(src);
1834 strcatW(src, fd.cFileName);
1837 if (param2[0] == '*') {
1838 strcatW(dest, fd.cFileName);
1839 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1841 strcatW(dest, param2);
1842 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1845 /* Build Extension */
1846 if (dotDst && (*(dotDst+1)=='*')) {
1847 if (dotSrc) strcatW(dest, dotSrc);
1848 } else if (dotDst) {
1849 if (dotDst) strcatW(dest, dotDst);
1852 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1853 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1855 /* Check if file is read only, otherwise move it */
1856 attribs = GetFileAttributesW(src);
1857 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1858 (attribs & FILE_ATTRIBUTE_READONLY)) {
1859 SetLastError(ERROR_ACCESS_DENIED);
1862 status = MoveFileW(src, dest);
1866 WCMD_print_error ();
1870 /* Step on to next match */
1871 if (FindNextFileW(hff, &fd) == 0) {
1873 hff = INVALID_HANDLE_VALUE;
1879 /*****************************************************************************
1882 * Make a copy of the environment.
1884 static WCHAR *WCMD_dupenv( const WCHAR *env )
1894 len += (strlenW(&env[len]) + 1);
1896 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1899 WINE_ERR("out of memory\n");
1902 memcpy (env_copy, env, len*sizeof (WCHAR));
1908 /*****************************************************************************
1911 * setlocal pushes the environment onto a stack
1912 * Save the environment as unicode so we don't screw anything up.
1914 void WCMD_setlocal (const WCHAR *s) {
1916 struct env_stack *env_copy;
1917 WCHAR cwd[MAX_PATH];
1919 /* DISABLEEXTENSIONS ignored */
1921 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1924 WINE_ERR ("out of memory\n");
1928 env = GetEnvironmentStringsW ();
1930 env_copy->strings = WCMD_dupenv (env);
1931 if (env_copy->strings)
1933 env_copy->next = saved_environment;
1934 saved_environment = env_copy;
1936 /* Save the current drive letter */
1937 GetCurrentDirectoryW(MAX_PATH, cwd);
1938 env_copy->u.cwd = cwd[0];
1941 LocalFree (env_copy);
1943 FreeEnvironmentStringsW (env);
1947 /*****************************************************************************
1950 * endlocal pops the environment off a stack
1951 * Note: When searching for '=', search from WCHAR position 1, to handle
1952 * special internal environment variables =C:, =D: etc
1954 void WCMD_endlocal (void) {
1955 WCHAR *env, *old, *p;
1956 struct env_stack *temp;
1959 if (!saved_environment)
1962 /* pop the old environment from the stack */
1963 temp = saved_environment;
1964 saved_environment = temp->next;
1966 /* delete the current environment, totally */
1967 env = GetEnvironmentStringsW ();
1968 old = WCMD_dupenv (GetEnvironmentStringsW ());
1971 n = strlenW(&old[len]) + 1;
1972 p = strchrW(&old[len] + 1, '=');
1976 SetEnvironmentVariableW (&old[len], NULL);
1981 FreeEnvironmentStringsW (env);
1983 /* restore old environment */
1984 env = temp->strings;
1987 n = strlenW(&env[len]) + 1;
1988 p = strchrW(&env[len] + 1, '=');
1992 SetEnvironmentVariableW (&env[len], p);
1997 /* Restore current drive letter */
1998 if (IsCharAlphaW(temp->u.cwd)) {
2000 WCHAR cwd[MAX_PATH];
2001 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2003 wsprintfW(envvar, fmt, temp->u.cwd);
2004 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2005 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2006 SetCurrentDirectoryW(cwd);
2014 /*****************************************************************************
2015 * WCMD_setshow_default
2017 * Set/Show the current default directory
2020 void WCMD_setshow_default (const WCHAR *command) {
2026 WIN32_FIND_DATAW fd;
2028 static const WCHAR parmD[] = {'/','D','\0'};
2030 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2032 /* Skip /D and trailing whitespace if on the front of the command line */
2033 if (CompareStringW(LOCALE_USER_DEFAULT,
2034 NORM_IGNORECASE | SORT_STRINGSORT,
2035 command, 2, parmD, -1) == CSTR_EQUAL) {
2037 while (*command && (*command==' ' || *command=='\t'))
2041 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2042 if (strlenW(command) == 0) {
2043 strcatW (cwd, newline);
2047 /* Remove any double quotes, which may be in the
2048 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2051 if (*command != '"') *pos++ = *command;
2054 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2058 /* Search for appropriate directory */
2059 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2060 hff = FindFirstFileW(string, &fd);
2061 while (hff != INVALID_HANDLE_VALUE) {
2062 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2063 WCHAR fpath[MAX_PATH];
2065 WCHAR dir[MAX_PATH];
2066 WCHAR fname[MAX_PATH];
2067 WCHAR ext[MAX_PATH];
2068 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2070 /* Convert path into actual directory spec */
2071 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2072 WCMD_splitpath(fpath, drive, dir, fname, ext);
2075 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2078 hff = INVALID_HANDLE_VALUE;
2082 /* Step on to next match */
2083 if (FindNextFileW(hff, &fd) == 0) {
2085 hff = INVALID_HANDLE_VALUE;
2090 /* Change to that directory */
2091 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2093 status = SetCurrentDirectoryW(string);
2096 WCMD_print_error ();
2100 /* Save away the actual new directory, to store as current location */
2101 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2103 /* Restore old directory if drive letter would change, and
2104 CD x:\directory /D (or pushd c:\directory) not supplied */
2105 if ((strstrW(quals, parmD) == NULL) &&
2106 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2107 SetCurrentDirectoryW(cwd);
2111 /* Set special =C: type environment variable, for drive letter of
2112 change of directory, even if path was restored due to missing
2113 /D (allows changing drive letter when not resident on that
2115 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2117 strcpyW(env, equalW);
2118 memcpy(env+1, string, 2 * sizeof(WCHAR));
2120 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2121 SetEnvironmentVariableW(env, string);
2128 /****************************************************************************
2131 * Set/Show the system date
2132 * FIXME: Can't change date yet
2135 void WCMD_setshow_date (void) {
2137 WCHAR curdate[64], buffer[64];
2139 static const WCHAR parmT[] = {'/','T','\0'};
2141 if (strlenW(param1) == 0) {
2142 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2143 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2144 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2145 if (strstrW (quals, parmT) == NULL) {
2146 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2147 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2148 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2150 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2154 else WCMD_print_error ();
2157 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2161 /****************************************************************************
2164 static int WCMD_compare( const void *a, const void *b )
2167 const WCHAR * const *str_a = a, * const *str_b = b;
2168 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2169 *str_a, -1, *str_b, -1 );
2170 if( r == CSTR_LESS_THAN ) return -1;
2171 if( r == CSTR_GREATER_THAN ) return 1;
2175 /****************************************************************************
2176 * WCMD_setshow_sortenv
2178 * sort variables into order for display
2179 * Optionally only display those who start with a stub
2180 * returns the count displayed
2182 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2184 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2187 if (stub) stublen = strlenW(stub);
2189 /* count the number of strings, and the total length */
2191 len += (strlenW(&s[len]) + 1);
2195 /* add the strings to an array */
2196 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2200 for( i=1; i<count; i++ )
2201 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2203 /* sort the array */
2204 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2207 for( i=0; i<count; i++ ) {
2208 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2209 NORM_IGNORECASE | SORT_STRINGSORT,
2210 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2211 /* Don't display special internal variables */
2212 if (str[i][0] != '=') {
2213 WCMD_output_asis(str[i]);
2214 WCMD_output_asis(newline);
2221 return displayedcount;
2224 /****************************************************************************
2227 * Set/Show the environment variables
2230 void WCMD_setshow_env (WCHAR *s) {
2235 static const WCHAR parmP[] = {'/','P','\0'};
2237 if (param1[0] == 0x00 && quals[0] == 0x00) {
2238 env = GetEnvironmentStringsW();
2239 WCMD_setshow_sortenv( env, NULL );
2243 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2244 if (CompareStringW(LOCALE_USER_DEFAULT,
2245 NORM_IGNORECASE | SORT_STRINGSORT,
2246 s, 2, parmP, -1) == CSTR_EQUAL) {
2247 WCHAR string[MAXSTRING];
2251 while (*s && (*s==' ' || *s=='\t')) s++;
2253 WCMD_opt_s_strip_quotes(s);
2255 /* If no parameter, or no '=' sign, return an error */
2256 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2257 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2261 /* Output the prompt */
2263 if (strlenW(p) != 0) WCMD_output(p);
2265 /* Read the reply */
2266 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2267 sizeof(string)/sizeof(WCHAR), &count, NULL);
2269 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2270 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2271 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2272 wine_dbgstr_w(string));
2273 status = SetEnvironmentVariableW(s, string);
2280 WCMD_opt_s_strip_quotes(s);
2281 p = strchrW (s, '=');
2283 env = GetEnvironmentStringsW();
2284 if (WCMD_setshow_sortenv( env, s ) == 0) {
2285 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2292 if (strlenW(p) == 0) p = NULL;
2293 status = SetEnvironmentVariableW(s, p);
2294 gle = GetLastError();
2295 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2297 } else if ((!status)) WCMD_print_error();
2301 /****************************************************************************
2304 * Set/Show the path environment variable
2307 void WCMD_setshow_path (const WCHAR *command) {
2311 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2312 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2314 if (strlenW(param1) == 0) {
2315 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2317 WCMD_output_asis ( pathEqW);
2318 WCMD_output_asis ( string);
2319 WCMD_output_asis ( newline);
2322 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2326 if (*command == '=') command++; /* Skip leading '=' */
2327 status = SetEnvironmentVariableW(pathW, command);
2328 if (!status) WCMD_print_error();
2332 /****************************************************************************
2333 * WCMD_setshow_prompt
2335 * Set or show the command prompt.
2338 void WCMD_setshow_prompt (void) {
2341 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2343 if (strlenW(param1) == 0) {
2344 SetEnvironmentVariableW(promptW, NULL);
2348 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2349 if (strlenW(s) == 0) {
2350 SetEnvironmentVariableW(promptW, NULL);
2352 else SetEnvironmentVariableW(promptW, s);
2356 /****************************************************************************
2359 * Set/Show the system time
2360 * FIXME: Can't change time yet
2363 void WCMD_setshow_time (void) {
2365 WCHAR curtime[64], buffer[64];
2368 static const WCHAR parmT[] = {'/','T','\0'};
2370 if (strlenW(param1) == 0) {
2372 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2373 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2374 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2375 if (strstrW (quals, parmT) == NULL) {
2376 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2377 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2378 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2380 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2384 else WCMD_print_error ();
2387 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2391 /****************************************************************************
2394 * Shift batch parameters.
2395 * Optional /n says where to start shifting (n=0-8)
2398 void WCMD_shift (const WCHAR *command) {
2401 if (context != NULL) {
2402 WCHAR *pos = strchrW(command, '/');
2407 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2408 start = (*(pos+1) - '0');
2410 SetLastError(ERROR_INVALID_PARAMETER);
2415 WINE_TRACE("Shifting variables, starting at %d\n", start);
2416 for (i=start;i<=8;i++) {
2417 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2419 context -> shift_count[9] = context -> shift_count[9] + 1;
2424 /****************************************************************************
2427 * Set the console title
2429 void WCMD_title (const WCHAR *command) {
2430 SetConsoleTitleW(command);
2433 /****************************************************************************
2436 * Copy a file to standard output.
2439 void WCMD_type (WCHAR *command) {
2442 WCHAR *argN = command;
2443 BOOL writeHeaders = FALSE;
2445 if (param1[0] == 0x00) {
2446 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2450 if (param2[0] != 0x00) writeHeaders = TRUE;
2452 /* Loop through all args */
2455 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2463 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2464 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2465 FILE_ATTRIBUTE_NORMAL, NULL);
2466 if (h == INVALID_HANDLE_VALUE) {
2467 WCMD_print_error ();
2468 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2472 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2473 WCMD_output(fmt, thisArg);
2475 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2476 if (count == 0) break; /* ReadFile reports success on EOF! */
2478 WCMD_output_asis (buffer);
2485 /****************************************************************************
2488 * Output either a file or stdin to screen in pages
2491 void WCMD_more (WCHAR *command) {
2494 WCHAR *argN = command;
2496 WCHAR moreStrPage[100];
2499 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2500 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2501 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2502 ')',' ','-','-','\n','\0'};
2503 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2505 /* Prefix the NLS more with '-- ', then load the text */
2507 strcpyW(moreStr, moreStart);
2508 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2509 (sizeof(moreStr)/sizeof(WCHAR))-3);
2511 if (param1[0] == 0x00) {
2513 /* Wine implements pipes via temporary files, and hence stdin is
2514 effectively reading from the file. This means the prompts for
2515 more are satisfied by the next line from the input (file). To
2516 avoid this, ensure stdin is to the console */
2517 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2518 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2519 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2520 FILE_ATTRIBUTE_NORMAL, 0);
2521 WINE_TRACE("No parms - working probably in pipe mode\n");
2522 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2524 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2525 once you get in this bit unless due to a pipe, its going to end badly... */
2526 wsprintfW(moreStrPage, moreFmt, moreStr);
2528 WCMD_enter_paged_mode(moreStrPage);
2529 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2530 if (count == 0) break; /* ReadFile reports success on EOF! */
2532 WCMD_output_asis (buffer);
2534 WCMD_leave_paged_mode();
2536 /* Restore stdin to what it was */
2537 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2538 CloseHandle(hConIn);
2542 BOOL needsPause = FALSE;
2544 /* Loop through all args */
2545 WINE_TRACE("Parms supplied - working through each file\n");
2546 WCMD_enter_paged_mode(moreStrPage);
2549 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2557 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2558 WCMD_leave_paged_mode();
2559 WCMD_output_asis(moreStrPage);
2560 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2561 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2562 WCMD_enter_paged_mode(moreStrPage);
2566 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2567 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2568 FILE_ATTRIBUTE_NORMAL, NULL);
2569 if (h == INVALID_HANDLE_VALUE) {
2570 WCMD_print_error ();
2571 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2575 ULONG64 fileLen = 0;
2576 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2578 /* Get the file size */
2579 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2580 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2583 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2584 if (count == 0) break; /* ReadFile reports success on EOF! */
2588 /* Update % count (would be used in WCMD_output_asis as prompt) */
2589 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2591 WCMD_output_asis (buffer);
2597 WCMD_leave_paged_mode();
2601 /****************************************************************************
2604 * Display verify flag.
2605 * FIXME: We don't actually do anything with the verify flag other than toggle
2609 void WCMD_verify (const WCHAR *command) {
2613 count = strlenW(command);
2615 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2616 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2619 if (lstrcmpiW(command, onW) == 0) {
2623 else if (lstrcmpiW(command, offW) == 0) {
2627 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2630 /****************************************************************************
2633 * Display version info.
2636 void WCMD_version (void) {
2638 WCMD_output (version_string);
2642 /****************************************************************************
2645 * Display volume info and/or set volume label. Returns 0 if error.
2648 int WCMD_volume (int mode, const WCHAR *path) {
2650 DWORD count, serial;
2651 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2654 if (strlenW(path) == 0) {
2655 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2657 WCMD_print_error ();
2660 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2661 &serial, NULL, NULL, NULL, 0);
2664 static const WCHAR fmt[] = {'%','s','\\','\0'};
2665 if ((path[1] != ':') || (strlenW(path) != 2)) {
2666 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2669 wsprintfW (curdir, fmt, path);
2670 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2675 WCMD_print_error ();
2678 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2679 curdir[0], label, HIWORD(serial), LOWORD(serial));
2681 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2682 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2683 sizeof(string)/sizeof(WCHAR), &count, NULL);
2685 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2686 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2688 if (strlenW(path) != 0) {
2689 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2692 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2698 /**************************************************************************
2701 * Exit either the process, or just this batch program
2705 void WCMD_exit (CMD_LIST **cmdList) {
2707 static const WCHAR parmB[] = {'/','B','\0'};
2708 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2710 if (context && lstrcmpiW(quals, parmB) == 0) {
2712 context -> skip_rest = TRUE;
2720 /*****************************************************************************
2723 * Lists or sets file associations (assoc = TRUE)
2724 * Lists or sets file types (assoc = FALSE)
2726 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2729 DWORD accessOptions = KEY_READ;
2731 LONG rc = ERROR_SUCCESS;
2732 WCHAR keyValue[MAXSTRING];
2733 DWORD valueLen = MAXSTRING;
2735 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2736 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2738 /* See if parameter includes '=' */
2740 newValue = strchrW(command, '=');
2741 if (newValue) accessOptions |= KEY_WRITE;
2743 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2744 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2745 accessOptions, &key) != ERROR_SUCCESS) {
2746 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2750 /* If no parameters then list all associations */
2751 if (*command == 0x00) {
2754 /* Enumerate all the keys */
2755 while (rc != ERROR_NO_MORE_ITEMS) {
2756 WCHAR keyName[MAXSTRING];
2759 /* Find the next value */
2760 nameLen = MAXSTRING;
2761 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2763 if (rc == ERROR_SUCCESS) {
2765 /* Only interested in extension ones if assoc, or others
2767 if ((keyName[0] == '.' && assoc) ||
2768 (!(keyName[0] == '.') && (!assoc)))
2770 WCHAR subkey[MAXSTRING];
2771 strcpyW(subkey, keyName);
2772 if (!assoc) strcatW(subkey, shOpCmdW);
2774 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2776 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2777 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2778 WCMD_output_asis(keyName);
2779 WCMD_output_asis(equalW);
2780 /* If no default value found, leave line empty after '=' */
2781 if (rc == ERROR_SUCCESS) {
2782 WCMD_output_asis(keyValue);
2784 WCMD_output_asis(newline);
2785 RegCloseKey(readKey);
2793 /* Parameter supplied - if no '=' on command line, its a query */
2794 if (newValue == NULL) {
2796 WCHAR subkey[MAXSTRING];
2798 /* Query terminates the parameter at the first space */
2799 strcpyW(keyValue, command);
2800 space = strchrW(keyValue, ' ');
2801 if (space) *space=0x00;
2803 /* Set up key name */
2804 strcpyW(subkey, keyValue);
2805 if (!assoc) strcatW(subkey, shOpCmdW);
2807 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2809 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2810 WCMD_output_asis(command);
2811 WCMD_output_asis(equalW);
2812 /* If no default value found, leave line empty after '=' */
2813 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2814 WCMD_output_asis(newline);
2815 RegCloseKey(readKey);
2818 WCHAR msgbuffer[MAXSTRING];
2819 WCHAR outbuffer[MAXSTRING];
2821 /* Load the translated 'File association not found' */
2823 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2825 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2827 wsprintfW(outbuffer, msgbuffer, keyValue);
2828 WCMD_output_asis_stderr(outbuffer);
2832 /* Not a query - its a set or clear of a value */
2835 WCHAR subkey[MAXSTRING];
2837 /* Get pointer to new value */
2841 /* Set up key name */
2842 strcpyW(subkey, command);
2843 if (!assoc) strcatW(subkey, shOpCmdW);
2845 /* If nothing after '=' then clear value - only valid for ASSOC */
2846 if (*newValue == 0x00) {
2848 if (assoc) rc = RegDeleteKeyW(key, command);
2849 if (assoc && rc == ERROR_SUCCESS) {
2850 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2852 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2857 WCHAR msgbuffer[MAXSTRING];
2858 WCHAR outbuffer[MAXSTRING];
2860 /* Load the translated 'File association not found' */
2862 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2863 sizeof(msgbuffer)/sizeof(WCHAR));
2865 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2866 sizeof(msgbuffer)/sizeof(WCHAR));
2868 wsprintfW(outbuffer, msgbuffer, keyValue);
2869 WCMD_output_asis_stderr(outbuffer);
2873 /* It really is a set value = contents */
2875 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2876 accessOptions, NULL, &readKey, NULL);
2877 if (rc == ERROR_SUCCESS) {
2878 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2880 sizeof(WCHAR) * (strlenW(newValue) + 1));
2881 RegCloseKey(readKey);
2884 if (rc != ERROR_SUCCESS) {
2888 WCMD_output_asis(command);
2889 WCMD_output_asis(equalW);
2890 WCMD_output_asis(newValue);
2891 WCMD_output_asis(newline);
2901 /****************************************************************************
2904 * Clear the terminal screen.
2907 void WCMD_color (void) {
2909 /* Emulate by filling the screen from the top left to bottom right with
2910 spaces, then moving the cursor to the top left afterwards */
2911 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2912 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2914 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2915 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2919 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2925 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2930 /* Convert the color hex digits */
2931 if (param1[0] == 0x00) {
2932 color = defaultColor;
2934 color = strtoulW(param1, NULL, 16);
2937 /* Fail if fg == bg color */
2938 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2943 /* Set the current screen contents and ensure all future writes
2944 remain this color */
2945 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2946 SetConsoleTextAttribute(hStdOut, color);