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 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 BOOL verify_mode = FALSE;
61 static const WCHAR dotW[] = {'.','\0'};
62 static const WCHAR dotdotW[] = {'.','.','\0'};
63 static const WCHAR slashW[] = {'\\','\0'};
64 static const WCHAR starW[] = {'*','\0'};
65 static const WCHAR equalW[] = {'=','\0'};
66 static const WCHAR fslashW[] = {'/','\0'};
67 static const WCHAR onW[] = {'O','N','\0'};
68 static const WCHAR offW[] = {'O','F','F','\0'};
69 static const WCHAR parmY[] = {'/','Y','\0'};
70 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
71 static const WCHAR nullW[] = {'\0'};
73 /**************************************************************************
76 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
79 * Returns True if Y (or A) answer is selected
80 * If optionAll contains a pointer, ALL is allowed, and if answered
84 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
85 const BOOL *optionAll) {
87 WCHAR msgbuffer[MAXSTRING];
88 WCHAR Ybuffer[MAXSTRING];
89 WCHAR Nbuffer[MAXSTRING];
90 WCHAR Abuffer[MAXSTRING];
91 WCHAR answer[MAX_PATH] = {'\0'};
94 /* Load the translated 'Are you sure', plus valid answers */
95 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
96 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
97 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
98 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
100 /* Loop waiting on a Y or N */
101 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
102 static const WCHAR startBkt[] = {' ','(','\0'};
103 static const WCHAR endBkt[] = {')','?','\0'};
105 WCMD_output_asis (message);
107 WCMD_output_asis (msgbuffer);
109 WCMD_output_asis (startBkt);
110 WCMD_output_asis (Ybuffer);
111 WCMD_output_asis (fslashW);
112 WCMD_output_asis (Nbuffer);
114 WCMD_output_asis (fslashW);
115 WCMD_output_asis (Abuffer);
117 WCMD_output_asis (endBkt);
118 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
119 answer[0] = toupperW(answer[0]);
122 /* Return the answer */
123 return ((answer[0] == Ybuffer[0]) ||
124 (optionAll && (answer[0] == Abuffer[0])));
127 /****************************************************************************
130 * Clear the terminal screen.
133 void WCMD_clear_screen (void) {
135 /* Emulate by filling the screen from the top left to bottom right with
136 spaces, then moving the cursor to the top left afterwards */
137 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
138 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
140 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
145 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
149 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
150 SetConsoleCursorPosition(hStdOut, topLeft);
154 /****************************************************************************
157 * Change the default i/o device (ie redirect STDin/STDout).
160 void WCMD_change_tty (void) {
162 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
166 /****************************************************************************
171 void WCMD_choice (const WCHAR * command) {
173 static const WCHAR bellW[] = {7,0};
174 static const WCHAR commaW[] = {',',0};
175 static const WCHAR bracket_open[] = {'[',0};
176 static const WCHAR bracket_close[] = {']','?',0};
181 WCHAR *my_command = NULL;
182 WCHAR opt_default = 0;
183 DWORD opt_timeout = 0;
190 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
193 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
197 ptr = WCMD_skip_leading_spaces(my_command);
198 while (*ptr == '/') {
199 switch (toupperW(ptr[1])) {
202 /* the colon is optional */
206 if (!*ptr || isspaceW(*ptr)) {
207 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
208 HeapFree(GetProcessHeap(), 0, my_command);
212 /* remember the allowed keys (overwrite previous /C option) */
214 while (*ptr && (!isspaceW(*ptr)))
218 /* terminate allowed chars */
220 ptr = WCMD_skip_leading_spaces(&ptr[1]);
222 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
227 ptr = WCMD_skip_leading_spaces(&ptr[2]);
232 ptr = WCMD_skip_leading_spaces(&ptr[2]);
237 /* the colon is optional */
241 opt_default = *ptr++;
243 if (!opt_default || (*ptr != ',')) {
244 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
245 HeapFree(GetProcessHeap(), 0, my_command);
251 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
257 opt_timeout = atoiW(answer);
259 ptr = WCMD_skip_leading_spaces(ptr);
263 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
264 HeapFree(GetProcessHeap(), 0, my_command);
270 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
273 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
275 /* use default keys, when needed: localized versions of "Y"es and "No" */
277 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
278 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
283 /* print the question, when needed */
285 WCMD_output_asis(ptr);
289 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
293 /* print a list of all allowed answers inside brackets */
294 WCMD_output_asis(bracket_open);
297 while ((answer[0] = *ptr++)) {
298 WCMD_output_asis(answer);
300 WCMD_output_asis(commaW);
302 WCMD_output_asis(bracket_close);
307 /* FIXME: Add support for option /T */
308 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
311 answer[0] = toupperW(answer[0]);
313 ptr = strchrW(opt_c, answer[0]);
315 WCMD_output_asis(answer);
316 WCMD_output(newline);
318 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
320 errorlevel = (ptr - opt_c) + 1;
321 WINE_TRACE("answer: %d\n", errorlevel);
322 HeapFree(GetProcessHeap(), 0, my_command);
327 /* key not allowed: play the bell */
328 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
329 WCMD_output_asis(bellW);
334 /****************************************************************************
337 * Copy a file or wildcarded set.
338 * FIXME: Add support for a+b+c type syntax
341 void WCMD_copy (void) {
346 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
348 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
349 BOOL copyToDir = FALSE;
350 WCHAR srcspec[MAX_PATH];
354 WCHAR fname[MAX_PATH];
357 if (param1[0] == 0x00) {
358 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
362 /* Convert source into full spec */
363 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
364 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
365 if (srcpath[strlenW(srcpath) - 1] == '\\')
366 srcpath[strlenW(srcpath) - 1] = '\0';
368 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
369 attribs = GetFileAttributesW(srcpath);
373 strcpyW(srcspec, srcpath);
375 /* If a directory, then add \* on the end when searching */
376 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
377 strcatW(srcpath, slashW);
378 strcatW(srcspec, slashW);
379 strcatW(srcspec, starW);
381 WCMD_splitpath(srcpath, drive, dir, fname, ext);
382 strcpyW(srcpath, drive);
383 strcatW(srcpath, dir);
386 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
388 /* If no destination supplied, assume current directory */
389 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
390 if (param2[0] == 0x00) {
391 strcpyW(param2, dotW);
394 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
395 if (outpath[strlenW(outpath) - 1] == '\\')
396 outpath[strlenW(outpath) - 1] = '\0';
397 attribs = GetFileAttributesW(outpath);
398 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
399 strcatW (outpath, slashW);
402 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
403 wine_dbgstr_w(outpath), copyToDir);
405 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
406 if (strstrW (quals, parmNoY))
408 else if (strstrW (quals, parmY))
411 /* By default, we will force the overwrite in batch mode and ask for
412 * confirmation in interactive mode. */
415 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
416 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
417 * default behavior. */
418 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
419 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
420 if (!lstrcmpiW (copycmd, parmY))
422 else if (!lstrcmpiW (copycmd, parmNoY))
427 /* Loop through all source files */
428 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
429 hff = FindFirstFileW(srcspec, &fd);
430 if (hff != INVALID_HANDLE_VALUE) {
432 WCHAR outname[MAX_PATH];
433 WCHAR srcname[MAX_PATH];
434 BOOL overwrite = force;
436 /* Destination is either supplied filename, or source name in
437 supplied destination directory */
438 strcpyW(outname, outpath);
439 if (copyToDir) strcatW(outname, fd.cFileName);
440 strcpyW(srcname, srcpath);
441 strcatW(srcname, fd.cFileName);
443 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
444 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
446 /* Skip . and .., and directories */
447 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
449 WINE_TRACE("Skipping directories\n");
452 /* Prompt before overwriting */
453 else if (!overwrite) {
454 attribs = GetFileAttributesW(outname);
455 if (attribs != INVALID_FILE_ATTRIBUTES) {
456 WCHAR buffer[MAXSTRING];
457 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
458 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
460 else overwrite = TRUE;
463 /* Do the copy as appropriate */
465 status = CopyFileW(srcname, outname, FALSE);
466 if (!status) WCMD_print_error ();
469 } while (FindNextFileW(hff, &fd) != 0);
472 status = ERROR_FILE_NOT_FOUND;
477 /****************************************************************************
480 * Create a directory (and, if needed, any intermediate directories).
482 * Modifies its argument by replacing slashes temporarily with nulls.
485 static BOOL create_full_path(WCHAR* path)
489 /* don't mess with drive letter portion of path, if any */
494 /* Strip trailing slashes. */
495 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
498 /* Step through path, creating intermediate directories as needed. */
499 /* First component includes drive letter, if any. */
503 /* Skip to end of component */
504 while (*p == '\\') p++;
505 while (*p && *p != '\\') p++;
507 /* path is now the original full path */
508 return CreateDirectoryW(path, NULL);
510 /* Truncate path, create intermediate directory, and restore path */
512 rv = CreateDirectoryW(path, NULL);
514 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
521 void WCMD_create_dir (WCHAR *command) {
523 WCHAR *argN = command;
525 if (param1[0] == 0x00) {
526 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
529 /* Loop through all args */
531 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL);
533 if (!create_full_path(thisArg)) {
540 /* Parse the /A options given by the user on the commandline
541 * into a bitmask of wanted attributes (*wantSet),
542 * and a bitmask of unwanted attributes (*wantClear).
544 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
545 static const WCHAR parmA[] = {'/','A','\0'};
548 /* both are strictly 'out' parameters */
552 /* For each /A argument */
553 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
557 /* Skip optional : */
560 /* For each of the attribute specifier chars to this /A option */
561 for (; *p != 0 && *p != '/'; p++) {
570 /* Convert the attribute specifier to a bit in one of the masks */
572 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
573 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
574 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
575 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
577 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
587 /* If filename part of parameter is * or *.*,
588 * and neither /Q nor /P options were given,
589 * prompt the user whether to proceed.
590 * Returns FALSE if user says no, TRUE otherwise.
591 * *pPrompted is set to TRUE if the user is prompted.
592 * (If /P supplied, del will prompt for individual files later.)
594 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
595 static const WCHAR parmP[] = {'/','P','\0'};
596 static const WCHAR parmQ[] = {'/','Q','\0'};
598 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
599 static const WCHAR anyExt[]= {'.','*','\0'};
602 WCHAR fname[MAX_PATH];
604 WCHAR fpath[MAX_PATH];
606 /* Convert path into actual directory spec */
607 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
608 WCMD_splitpath(fpath, drive, dir, fname, ext);
610 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
611 if ((strcmpW(fname, starW) == 0) &&
612 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
614 WCHAR question[MAXSTRING];
615 static const WCHAR fmt[] = {'%','s',' ','\0'};
617 /* Caller uses this to suppress "file not found" warning later */
620 /* Ask for confirmation */
621 wsprintfW(question, fmt, fpath);
622 return WCMD_ask_confirm(question, TRUE, NULL);
625 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
629 /* Helper function for WCMD_delete().
630 * Deletes a single file, directory, or wildcard.
631 * If /S was given, does it recursively.
632 * Returns TRUE if a file was deleted.
634 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
636 static const WCHAR parmP[] = {'/','P','\0'};
637 static const WCHAR parmS[] = {'/','S','\0'};
638 static const WCHAR parmF[] = {'/','F','\0'};
640 DWORD unwanted_attrs;
642 WCHAR argCopy[MAX_PATH];
645 WCHAR fpath[MAX_PATH];
647 BOOL handleParm = TRUE;
649 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
651 strcpyW(argCopy, thisArg);
652 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
653 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
655 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
656 /* Skip this arg if user declines to delete *.* */
660 /* First, try to delete in the current directory */
661 hff = FindFirstFileW(argCopy, &fd);
662 if (hff == INVALID_HANDLE_VALUE) {
668 /* Support del <dirname> by just deleting all files dirname\* */
670 && (strchrW(argCopy,'*') == NULL)
671 && (strchrW(argCopy,'?') == NULL)
672 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
674 WCHAR modifiedParm[MAX_PATH];
675 static const WCHAR slashStar[] = {'\\','*','\0'};
677 strcpyW(modifiedParm, argCopy);
678 strcatW(modifiedParm, slashStar);
681 WCMD_delete_one(modifiedParm);
683 } else if (handleParm) {
685 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
686 strcpyW (fpath, argCopy);
688 p = strrchrW (fpath, '\\');
691 strcatW (fpath, fd.cFileName);
693 else strcpyW (fpath, fd.cFileName);
694 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
697 /* Handle attribute matching (/A) */
698 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
699 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
701 /* /P means prompt for each file */
702 if (ok && strstrW (quals, parmP) != NULL) {
703 WCHAR question[MAXSTRING];
705 /* Ask for confirmation */
706 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
707 ok = WCMD_ask_confirm(question, FALSE, NULL);
710 /* Only proceed if ok to */
713 /* If file is read only, and /A:r or /F supplied, delete it */
714 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
715 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
716 strstrW (quals, parmF) != NULL)) {
717 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
720 /* Now do the delete */
721 if (!DeleteFileW(fpath)) WCMD_print_error ();
725 } while (FindNextFileW(hff, &fd) != 0);
729 /* Now recurse into all subdirectories handling the parameter in the same way */
730 if (strstrW (quals, parmS) != NULL) {
732 WCHAR thisDir[MAX_PATH];
737 WCHAR fname[MAX_PATH];
740 /* Convert path into actual directory spec */
741 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
742 WCMD_splitpath(thisDir, drive, dir, fname, ext);
744 strcpyW(thisDir, drive);
745 strcatW(thisDir, dir);
746 cPos = strlenW(thisDir);
748 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
750 /* Append '*' to the directory */
752 thisDir[cPos+1] = 0x00;
754 hff = FindFirstFileW(thisDir, &fd);
756 /* Remove residual '*' */
757 thisDir[cPos] = 0x00;
759 if (hff != INVALID_HANDLE_VALUE) {
760 DIRECTORY_STACK *allDirs = NULL;
761 DIRECTORY_STACK *lastEntry = NULL;
764 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
765 (strcmpW(fd.cFileName, dotdotW) != 0) &&
766 (strcmpW(fd.cFileName, dotW) != 0)) {
768 DIRECTORY_STACK *nextDir;
769 WCHAR subParm[MAX_PATH];
771 /* Work out search parameter in sub dir */
772 strcpyW (subParm, thisDir);
773 strcatW (subParm, fd.cFileName);
774 strcatW (subParm, slashW);
775 strcatW (subParm, fname);
776 strcatW (subParm, ext);
777 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
779 /* Allocate memory, add to list */
780 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
781 if (allDirs == NULL) allDirs = nextDir;
782 if (lastEntry != NULL) lastEntry->next = nextDir;
784 nextDir->next = NULL;
785 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
786 (strlenW(subParm)+1) * sizeof(WCHAR));
787 strcpyW(nextDir->dirName, subParm);
789 } while (FindNextFileW(hff, &fd) != 0);
792 /* Go through each subdir doing the delete */
793 while (allDirs != NULL) {
794 DIRECTORY_STACK *tempDir;
796 tempDir = allDirs->next;
797 found |= WCMD_delete_one (allDirs->dirName);
799 HeapFree(GetProcessHeap(),0,allDirs->dirName);
800 HeapFree(GetProcessHeap(),0,allDirs);
809 /****************************************************************************
812 * Delete a file or wildcarded set.
815 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
816 * - Each set is a pattern, eg /ahr /as-r means
817 * readonly+hidden OR nonreadonly system files
818 * - The '-' applies to a single field, ie /a:-hr means read only
822 BOOL WCMD_delete (WCHAR *command) {
825 BOOL argsProcessed = FALSE;
826 BOOL foundAny = FALSE;
830 for (argno=0; ; argno++) {
835 thisArg = WCMD_parameter (command, argno, &argN, NULL);
837 break; /* no more parameters */
839 continue; /* skip options */
841 argsProcessed = TRUE;
842 found = WCMD_delete_one(thisArg);
845 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
850 /* Handle no valid args */
852 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
857 /****************************************************************************
860 * Echo input to the screen (or not). We don't try to emulate the bugs
861 * in DOS (try typing "ECHO ON AGAIN" for an example).
864 void WCMD_echo (const WCHAR *command) {
867 const WCHAR *origcommand = command;
869 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
870 || command[0]==':' || command[0]==';')
873 count = strlenW(command);
874 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
875 && origcommand[0]!=';') {
876 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
877 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
880 if (lstrcmpiW(command, onW) == 0) {
884 if (lstrcmpiW(command, offW) == 0) {
888 WCMD_output_asis (command);
889 WCMD_output (newline);
893 /**************************************************************************
896 * Batch file loop processing.
898 * On entry: cmdList contains the syntax up to the set
899 * next cmdList and all in that bracket contain the set data
900 * next cmdlist contains the DO cmd
901 * following that is either brackets or && entries (as per if)
905 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
910 static const WCHAR inW[] = {'i','n'};
911 static const WCHAR doW[] = {'d','o'};
912 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
918 BOOL expandDirs = FALSE;
919 BOOL useNumbers = FALSE;
920 BOOL doFileset = FALSE;
921 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
923 CMD_LIST *thisCmdStart;
926 /* Handle optional qualifiers (multiple are allowed) */
927 while (*curPos && *curPos == '/') {
928 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
930 switch (toupperW(*curPos)) {
931 case 'D': curPos++; expandDirs = TRUE; break;
932 case 'L': curPos++; useNumbers = TRUE; break;
934 /* Recursive is special case - /R can have an optional path following it */
935 /* filenamesets are another special case - /F can have an optional options following it */
939 BOOL isRecursive = (*curPos == 'R');
944 /* Skip whitespace */
946 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
948 /* Next parm is either qualifier, path/options or variable -
949 only care about it if it is the path/options */
950 if (*curPos && *curPos != '/' && *curPos != '%') {
951 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
953 static unsigned int once;
954 if (!once++) WINE_FIXME("/F needs to handle options\n");
960 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
964 /* Skip whitespace between qualifiers */
965 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
968 /* Skip whitespace before variable */
969 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
971 /* Ensure line continues with variable */
972 if (!*curPos || *curPos != '%') {
973 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
977 /* Variable should follow */
979 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
980 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
982 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
985 /* Skip whitespace before IN */
986 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
988 /* Ensure line continues with IN */
990 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
992 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
996 /* Save away where the set of data starts and the variable */
997 thisDepth = (*cmdList)->bracketDepth;
998 *cmdList = (*cmdList)->nextcommand;
999 setStart = (*cmdList);
1001 /* Skip until the close bracket */
1002 WINE_TRACE("Searching %p as the set\n", *cmdList);
1004 (*cmdList)->command != NULL &&
1005 (*cmdList)->bracketDepth > thisDepth) {
1006 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1007 *cmdList = (*cmdList)->nextcommand;
1010 /* Skip the close bracket, if there is one */
1011 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1013 /* Syntax error if missing close bracket, or nothing following it
1014 and once we have the complete set, we expect a DO */
1015 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1016 if ((*cmdList == NULL)
1017 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1019 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1023 /* Save away the starting position for the commands (and offset for the
1025 cmdStart = *cmdList;
1027 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1031 /* Loop through all set entries */
1033 thisSet->command != NULL &&
1034 thisSet->bracketDepth >= thisDepth) {
1036 /* Loop through all entries on the same line */
1040 WINE_TRACE("Processing for set %p\n", thisSet);
1042 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1045 * If the parameter within the set has a wildcard then search for matching files
1046 * otherwise do a literal substitution.
1048 static const WCHAR wildcards[] = {'*','?','\0'};
1049 thisCmdStart = cmdStart;
1052 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1054 if (!useNumbers && !doFileset) {
1055 if (strpbrkW (item, wildcards)) {
1056 hff = FindFirstFileW(item, &fd);
1057 if (hff != INVALID_HANDLE_VALUE) {
1059 BOOL isDirectory = FALSE;
1061 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1063 /* Handle as files or dirs appropriately, but ignore . and .. */
1064 if (isDirectory == expandDirs &&
1065 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1066 (strcmpW(fd.cFileName, dotW) != 0))
1068 thisCmdStart = cmdStart;
1069 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1070 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1071 fd.cFileName, FALSE, TRUE);
1074 } while (FindNextFileW(hff, &fd) != 0);
1078 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1081 } else if (useNumbers) {
1082 /* Convert the first 3 numbers to signed longs and save */
1083 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1084 /* else ignore them! */
1086 /* Filesets - either a list of files, or a command to run and parse the output */
1087 } else if (doFileset && *itemStart != '"') {
1090 WCHAR temp_file[MAX_PATH];
1092 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1093 wine_dbgstr_w(item));
1095 /* If backquote or single quote, we need to launch that command
1096 and parse the results - use a temporary file */
1097 if (*itemStart == '`' || *itemStart == '\'') {
1099 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1100 static const WCHAR redirOut[] = {'>','%','s','\0'};
1101 static const WCHAR cmdW[] = {'C','M','D','\0'};
1103 /* Remove trailing character */
1104 itemStart[strlenW(itemStart)-1] = 0x00;
1106 /* Get temp filename */
1107 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1108 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1110 /* Execute program and redirect output */
1111 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1112 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1114 /* Open the file, read line by line and process */
1115 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1116 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1119 /* Open the file, read line by line and process */
1120 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1121 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1124 /* Process the input file */
1125 if (input == INVALID_HANDLE_VALUE) {
1126 WCMD_print_error ();
1127 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1129 return; /* FOR loop aborts at first failure here */
1133 WCHAR buffer[MAXSTRING] = {'\0'};
1134 WCHAR *where, *parm;
1136 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1138 /* Skip blank lines*/
1139 parm = WCMD_parameter (buffer, 0, &where, NULL);
1140 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1141 wine_dbgstr_w(buffer));
1144 /* FIXME: The following should be moved into its own routine and
1145 reused for the string literal parsing below */
1146 thisCmdStart = cmdStart;
1147 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1148 cmdEnd = thisCmdStart;
1154 CloseHandle (input);
1157 /* Delete the temporary file */
1158 if (*itemStart == '`' || *itemStart == '\'') {
1159 DeleteFileW(temp_file);
1162 /* Filesets - A string literal */
1163 } else if (doFileset && *itemStart == '"') {
1164 WCHAR buffer[MAXSTRING] = {'\0'};
1165 WCHAR *where, *parm;
1167 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1168 strcpyW(buffer, item);
1169 parm = WCMD_parameter (buffer, 0, &where, NULL);
1170 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1171 wine_dbgstr_w(buffer));
1174 /* FIXME: The following should be moved into its own routine and
1175 reused for the string literal parsing below */
1176 thisCmdStart = cmdStart;
1177 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1178 cmdEnd = thisCmdStart;
1182 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1183 cmdEnd = thisCmdStart;
1187 /* Move onto the next set line */
1188 thisSet = thisSet->nextcommand;
1191 /* If /L is provided, now run the for loop */
1194 static const WCHAR fmt[] = {'%','d','\0'};
1196 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1197 numbers[0], numbers[2], numbers[1]);
1199 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1202 sprintfW(thisNum, fmt, i);
1203 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1205 thisCmdStart = cmdStart;
1206 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1207 cmdEnd = thisCmdStart;
1211 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1212 all processing, OR it should be pointing to the end of && processing OR
1213 it should be pointing at the NULL end of bracket for the DO. The return
1214 value needs to be the NEXT command to execute, which it either is, or
1215 we need to step over the closing bracket */
1217 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1221 /*****************************************************************************
1224 * Execute a command, and any && or bracketed follow on to the command. The
1225 * first command to be executed may not be at the front of the
1226 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1228 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1229 const WCHAR *variable, const WCHAR *value,
1230 BOOL isIF, BOOL conditionTRUE) {
1232 CMD_LIST *curPosition = *cmdList;
1233 int myDepth = (*cmdList)->bracketDepth;
1235 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1236 cmdList, wine_dbgstr_w(firstcmd),
1237 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1240 /* Skip leading whitespace between condition and the command */
1241 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1243 /* Process the first command, if there is one */
1244 if (conditionTRUE && firstcmd && *firstcmd) {
1245 WCHAR *command = WCMD_strdupW(firstcmd);
1246 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1247 HeapFree(GetProcessHeap(), 0, command);
1251 /* If it didn't move the position, step to next command */
1252 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1254 /* Process any other parts of the command */
1256 BOOL processThese = TRUE;
1258 if (isIF) processThese = conditionTRUE;
1261 static const WCHAR ifElse[] = {'e','l','s','e'};
1263 /* execute all appropriate commands */
1264 curPosition = *cmdList;
1266 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1268 (*cmdList)->prevDelim,
1269 (*cmdList)->bracketDepth, myDepth);
1271 /* Execute any statements appended to the line */
1272 /* FIXME: Only if previous call worked for && or failed for || */
1273 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1274 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1275 if (processThese && (*cmdList)->command) {
1276 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1279 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1281 /* Execute any appended to the statement with (...) */
1282 } else if ((*cmdList)->bracketDepth > myDepth) {
1284 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1285 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1287 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1289 /* End of the command - does 'ELSE ' follow as the next command? */
1292 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1293 (*cmdList)->command)) {
1295 /* Swap between if and else processing */
1296 processThese = !processThese;
1298 /* Process the ELSE part */
1300 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1301 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1303 /* Skip leading whitespace between condition and the command */
1304 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1306 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1309 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1311 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1320 /**************************************************************************
1323 * Simple on-line help. Help text is stored in the resource file.
1326 void WCMD_give_help (const WCHAR *command) {
1330 command = WCMD_skip_leading_spaces((WCHAR*) command);
1331 if (strlenW(command) == 0) {
1332 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1335 /* Display help message for builtin commands */
1336 for (i=0; i<=WCMD_EXIT; i++) {
1337 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1338 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1339 WCMD_output_asis (WCMD_LoadMessage(i));
1343 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1344 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1345 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1346 command, -1, externals[i], -1) == CSTR_EQUAL) {
1348 static const WCHAR helpW[] = {' ', '/','?','\0'};
1349 strcpyW(cmd, command);
1350 strcatW(cmd, helpW);
1351 WCMD_run_program(cmd, 0);
1355 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1360 /****************************************************************************
1363 * Batch file jump instruction. Not the most efficient algorithm ;-)
1364 * Prints error message if the specified label cannot be found - the file pointer is
1365 * then at EOF, effectively stopping the batch file.
1366 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1369 void WCMD_goto (CMD_LIST **cmdList) {
1371 WCHAR string[MAX_PATH];
1372 WCHAR current[MAX_PATH];
1374 /* Do not process any more parts of a processed multipart or multilines command */
1375 if (cmdList) *cmdList = NULL;
1377 if (context != NULL) {
1378 WCHAR *paramStart = param1, *str;
1379 static const WCHAR eofW[] = {':','e','o','f','\0'};
1381 if (param1[0] == 0x00) {
1382 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1386 /* Handle special :EOF label */
1387 if (lstrcmpiW (eofW, param1) == 0) {
1388 context -> skip_rest = TRUE;
1392 /* Support goto :label as well as goto label */
1393 if (*paramStart == ':') paramStart++;
1395 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1396 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1398 while (isspaceW (*str)) str++;
1402 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1405 /* ignore space at the end */
1407 if (lstrcmpiW (current, paramStart) == 0) return;
1410 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1415 /*****************************************************************************
1418 * Push a directory onto the stack
1421 void WCMD_pushd (WCHAR *command) {
1422 struct env_stack *curdir;
1424 static const WCHAR parmD[] = {'/','D','\0'};
1426 if (strchrW(command, '/') != NULL) {
1427 SetLastError(ERROR_INVALID_PARAMETER);
1432 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1433 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1434 if( !curdir || !thisdir ) {
1437 WINE_ERR ("out of memory\n");
1441 /* Change directory using CD code with /D parameter */
1442 strcpyW(quals, parmD);
1443 GetCurrentDirectoryW (1024, thisdir);
1445 WCMD_setshow_default(command);
1451 curdir -> next = pushd_directories;
1452 curdir -> strings = thisdir;
1453 if (pushd_directories == NULL) {
1454 curdir -> u.stackdepth = 1;
1456 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1458 pushd_directories = curdir;
1463 /*****************************************************************************
1466 * Pop a directory from the stack
1469 void WCMD_popd (void) {
1470 struct env_stack *temp = pushd_directories;
1472 if (!pushd_directories)
1475 /* pop the old environment from the stack, and make it the current dir */
1476 pushd_directories = temp->next;
1477 SetCurrentDirectoryW(temp->strings);
1478 LocalFree (temp->strings);
1482 /****************************************************************************
1485 * Batch file conditional.
1487 * On entry, cmdlist will point to command containing the IF, and optionally
1488 * the first command to execute (if brackets not found)
1489 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1490 * If ('s were found, execute all within that bracket
1491 * Command may optionally be followed by an ELSE - need to skip instructions
1492 * in the else using the same logic
1494 * FIXME: Much more syntax checking needed!
1497 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1499 int negate; /* Negate condition */
1500 int test; /* Condition evaluation result */
1501 WCHAR condition[MAX_PATH], *command, *s;
1502 static const WCHAR notW[] = {'n','o','t','\0'};
1503 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1504 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1505 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1506 static const WCHAR eqeqW[] = {'=','=','\0'};
1507 static const WCHAR parmI[] = {'/','I','\0'};
1508 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1510 negate = !lstrcmpiW(param1,notW);
1511 strcpyW(condition, (negate ? param2 : param1));
1512 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1514 if (!lstrcmpiW (condition, errlvlW)) {
1515 test = (errorlevel >= atoiW(WCMD_parameter(p, 1+negate, NULL, NULL)));
1516 WCMD_parameter(p, 2+negate, &command, NULL);
1518 else if (!lstrcmpiW (condition, existW)) {
1519 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1520 WCMD_parameter(p, 2+negate, &command, NULL);
1522 else if (!lstrcmpiW (condition, defdW)) {
1523 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1524 WCMD_parameter(p, 2+negate, &command, NULL);
1526 else if ((s = strstrW (p, eqeqW))) {
1527 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1528 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1530 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1531 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1532 test = caseInsensitive
1533 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1534 leftPart, leftPartEnd-leftPart+1,
1535 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1536 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1537 leftPart, leftPartEnd-leftPart+1,
1538 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1539 WCMD_parameter(s, 1, &command, NULL);
1542 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1546 /* Process rest of IF statement which is on the same line
1547 Note: This may process all or some of the cmdList (eg a GOTO) */
1548 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1551 /****************************************************************************
1554 * Move a file, directory tree or wildcarded set of files.
1557 void WCMD_move (void)
1560 WIN32_FIND_DATAW fd;
1562 WCHAR input[MAX_PATH];
1563 WCHAR output[MAX_PATH];
1565 WCHAR dir[MAX_PATH];
1566 WCHAR fname[MAX_PATH];
1567 WCHAR ext[MAX_PATH];
1569 if (param1[0] == 0x00) {
1570 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1574 /* If no destination supplied, assume current directory */
1575 if (param2[0] == 0x00) {
1576 strcpyW(param2, dotW);
1579 /* If 2nd parm is directory, then use original filename */
1580 /* Convert partial path to full path */
1581 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1582 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1583 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1584 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1586 /* Split into components */
1587 WCMD_splitpath(input, drive, dir, fname, ext);
1589 hff = FindFirstFileW(input, &fd);
1590 if (hff == INVALID_HANDLE_VALUE)
1594 WCHAR dest[MAX_PATH];
1595 WCHAR src[MAX_PATH];
1599 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1601 /* Build src & dest name */
1602 strcpyW(src, drive);
1605 /* See if dest is an existing directory */
1606 attribs = GetFileAttributesW(output);
1607 if (attribs != INVALID_FILE_ATTRIBUTES &&
1608 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1609 strcpyW(dest, output);
1610 strcatW(dest, slashW);
1611 strcatW(dest, fd.cFileName);
1613 strcpyW(dest, output);
1616 strcatW(src, fd.cFileName);
1618 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1619 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1621 /* If destination exists, prompt unless /Y supplied */
1622 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1624 WCHAR copycmd[MAXSTRING];
1627 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1628 if (strstrW (quals, parmNoY))
1630 else if (strstrW (quals, parmY))
1633 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1634 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1635 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1636 && ! lstrcmpiW (copycmd, parmY));
1639 /* Prompt if overwriting */
1641 WCHAR question[MAXSTRING];
1644 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1646 /* Ask for confirmation */
1647 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1648 ok = WCMD_ask_confirm(question, FALSE, NULL);
1650 /* So delete the destination prior to the move */
1652 if (!DeleteFileW(dest)) {
1653 WCMD_print_error ();
1662 status = MoveFileW(src, dest);
1664 status = 1; /* Anything other than 0 to prevent error msg below */
1668 WCMD_print_error ();
1671 } while (FindNextFileW(hff, &fd) != 0);
1676 /****************************************************************************
1679 * Suspend execution of a batch script until a key is typed
1682 void WCMD_pause (void)
1688 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1690 have_console = GetConsoleMode(hIn, &oldmode);
1692 SetConsoleMode(hIn, 0);
1694 WCMD_output(anykey);
1695 WCMD_ReadFile(hIn, &key, 1, &count);
1697 SetConsoleMode(hIn, oldmode);
1700 /****************************************************************************
1703 * Delete a directory.
1706 void WCMD_remove_dir (WCHAR *command) {
1709 int argsProcessed = 0;
1710 WCHAR *argN = command;
1711 static const WCHAR parmS[] = {'/','S','\0'};
1712 static const WCHAR parmQ[] = {'/','Q','\0'};
1714 /* Loop through all args */
1716 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1717 if (argN && argN[0] != '/') {
1718 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1719 wine_dbgstr_w(quals));
1722 /* If subdirectory search not supplied, just try to remove
1723 and report error if it fails (eg if it contains a file) */
1724 if (strstrW (quals, parmS) == NULL) {
1725 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1727 /* Otherwise use ShFileOp to recursively remove a directory */
1730 SHFILEOPSTRUCTW lpDir;
1733 if (strstrW (quals, parmQ) == NULL) {
1735 WCHAR question[MAXSTRING];
1736 static const WCHAR fmt[] = {'%','s',' ','\0'};
1738 /* Ask for confirmation */
1739 wsprintfW(question, fmt, thisArg);
1740 ok = WCMD_ask_confirm(question, TRUE, NULL);
1742 /* Abort if answer is 'N' */
1749 lpDir.pFrom = thisArg;
1750 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1751 lpDir.wFunc = FO_DELETE;
1752 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1757 /* Handle no valid args */
1758 if (argsProcessed == 0) {
1759 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1765 /****************************************************************************
1771 void WCMD_rename (void) {
1775 WIN32_FIND_DATAW fd;
1776 WCHAR input[MAX_PATH];
1777 WCHAR *dotDst = NULL;
1779 WCHAR dir[MAX_PATH];
1780 WCHAR fname[MAX_PATH];
1781 WCHAR ext[MAX_PATH];
1786 /* Must be at least two args */
1787 if (param1[0] == 0x00 || param2[0] == 0x00) {
1788 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1793 /* Destination cannot contain a drive letter or directory separator */
1794 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1795 SetLastError(ERROR_INVALID_PARAMETER);
1801 /* Convert partial path to full path */
1802 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1803 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1804 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1805 dotDst = strchrW(param2, '.');
1807 /* Split into components */
1808 WCMD_splitpath(input, drive, dir, fname, ext);
1810 hff = FindFirstFileW(input, &fd);
1811 if (hff == INVALID_HANDLE_VALUE)
1815 WCHAR dest[MAX_PATH];
1816 WCHAR src[MAX_PATH];
1817 WCHAR *dotSrc = NULL;
1820 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1822 /* FIXME: If dest name or extension is *, replace with filename/ext
1823 part otherwise use supplied name. This supports:
1825 ren jim.* fred.* etc
1826 However, windows has a more complex algorithm supporting eg
1827 ?'s and *'s mid name */
1828 dotSrc = strchrW(fd.cFileName, '.');
1830 /* Build src & dest name */
1831 strcpyW(src, drive);
1834 dirLen = strlenW(src);
1835 strcatW(src, fd.cFileName);
1838 if (param2[0] == '*') {
1839 strcatW(dest, fd.cFileName);
1840 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1842 strcatW(dest, param2);
1843 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1846 /* Build Extension */
1847 if (dotDst && (*(dotDst+1)=='*')) {
1848 if (dotSrc) strcatW(dest, dotSrc);
1849 } else if (dotDst) {
1850 if (dotDst) strcatW(dest, dotDst);
1853 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1854 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1856 /* Check if file is read only, otherwise move it */
1857 attribs = GetFileAttributesW(src);
1858 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1859 (attribs & FILE_ATTRIBUTE_READONLY)) {
1860 SetLastError(ERROR_ACCESS_DENIED);
1863 status = MoveFileW(src, dest);
1867 WCMD_print_error ();
1870 } while (FindNextFileW(hff, &fd) != 0);
1875 /*****************************************************************************
1878 * Make a copy of the environment.
1880 static WCHAR *WCMD_dupenv( const WCHAR *env )
1890 len += (strlenW(&env[len]) + 1);
1892 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1895 WINE_ERR("out of memory\n");
1898 memcpy (env_copy, env, len*sizeof (WCHAR));
1904 /*****************************************************************************
1907 * setlocal pushes the environment onto a stack
1908 * Save the environment as unicode so we don't screw anything up.
1910 void WCMD_setlocal (const WCHAR *s) {
1912 struct env_stack *env_copy;
1913 WCHAR cwd[MAX_PATH];
1915 /* DISABLEEXTENSIONS ignored */
1917 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1920 WINE_ERR ("out of memory\n");
1924 env = GetEnvironmentStringsW ();
1926 env_copy->strings = WCMD_dupenv (env);
1927 if (env_copy->strings)
1929 env_copy->next = saved_environment;
1930 saved_environment = env_copy;
1932 /* Save the current drive letter */
1933 GetCurrentDirectoryW(MAX_PATH, cwd);
1934 env_copy->u.cwd = cwd[0];
1937 LocalFree (env_copy);
1939 FreeEnvironmentStringsW (env);
1943 /*****************************************************************************
1946 * endlocal pops the environment off a stack
1947 * Note: When searching for '=', search from WCHAR position 1, to handle
1948 * special internal environment variables =C:, =D: etc
1950 void WCMD_endlocal (void) {
1951 WCHAR *env, *old, *p;
1952 struct env_stack *temp;
1955 if (!saved_environment)
1958 /* pop the old environment from the stack */
1959 temp = saved_environment;
1960 saved_environment = temp->next;
1962 /* delete the current environment, totally */
1963 env = GetEnvironmentStringsW ();
1964 old = WCMD_dupenv (GetEnvironmentStringsW ());
1967 n = strlenW(&old[len]) + 1;
1968 p = strchrW(&old[len] + 1, '=');
1972 SetEnvironmentVariableW (&old[len], NULL);
1977 FreeEnvironmentStringsW (env);
1979 /* restore old environment */
1980 env = temp->strings;
1983 n = strlenW(&env[len]) + 1;
1984 p = strchrW(&env[len] + 1, '=');
1988 SetEnvironmentVariableW (&env[len], p);
1993 /* Restore current drive letter */
1994 if (IsCharAlphaW(temp->u.cwd)) {
1996 WCHAR cwd[MAX_PATH];
1997 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1999 wsprintfW(envvar, fmt, temp->u.cwd);
2000 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2001 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2002 SetCurrentDirectoryW(cwd);
2010 /*****************************************************************************
2011 * WCMD_setshow_default
2013 * Set/Show the current default directory
2016 void WCMD_setshow_default (const WCHAR *command) {
2022 WIN32_FIND_DATAW fd;
2024 static const WCHAR parmD[] = {'/','D','\0'};
2026 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2028 /* Skip /D and trailing whitespace if on the front of the command line */
2029 if (CompareStringW(LOCALE_USER_DEFAULT,
2030 NORM_IGNORECASE | SORT_STRINGSORT,
2031 command, 2, parmD, -1) == CSTR_EQUAL) {
2033 while (*command && (*command==' ' || *command=='\t'))
2037 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2038 if (strlenW(command) == 0) {
2039 strcatW (cwd, newline);
2043 /* Remove any double quotes, which may be in the
2044 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2047 if (*command != '"') *pos++ = *command;
2050 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2054 /* Search for appropriate directory */
2055 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2056 hff = FindFirstFileW(string, &fd);
2057 if (hff != INVALID_HANDLE_VALUE) {
2059 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2060 WCHAR fpath[MAX_PATH];
2062 WCHAR dir[MAX_PATH];
2063 WCHAR fname[MAX_PATH];
2064 WCHAR ext[MAX_PATH];
2065 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2067 /* Convert path into actual directory spec */
2068 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2069 WCMD_splitpath(fpath, drive, dir, fname, ext);
2072 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2075 } while (FindNextFileW(hff, &fd) != 0);
2079 /* Change to that directory */
2080 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2082 status = SetCurrentDirectoryW(string);
2085 WCMD_print_error ();
2089 /* Save away the actual new directory, to store as current location */
2090 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2092 /* Restore old directory if drive letter would change, and
2093 CD x:\directory /D (or pushd c:\directory) not supplied */
2094 if ((strstrW(quals, parmD) == NULL) &&
2095 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2096 SetCurrentDirectoryW(cwd);
2100 /* Set special =C: type environment variable, for drive letter of
2101 change of directory, even if path was restored due to missing
2102 /D (allows changing drive letter when not resident on that
2104 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2106 strcpyW(env, equalW);
2107 memcpy(env+1, string, 2 * sizeof(WCHAR));
2109 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2110 SetEnvironmentVariableW(env, string);
2117 /****************************************************************************
2120 * Set/Show the system date
2121 * FIXME: Can't change date yet
2124 void WCMD_setshow_date (void) {
2126 WCHAR curdate[64], buffer[64];
2128 static const WCHAR parmT[] = {'/','T','\0'};
2130 if (strlenW(param1) == 0) {
2131 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2132 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2133 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2134 if (strstrW (quals, parmT) == NULL) {
2135 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2136 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2138 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2142 else WCMD_print_error ();
2145 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2149 /****************************************************************************
2152 static int WCMD_compare( const void *a, const void *b )
2155 const WCHAR * const *str_a = a, * const *str_b = b;
2156 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2157 *str_a, -1, *str_b, -1 );
2158 if( r == CSTR_LESS_THAN ) return -1;
2159 if( r == CSTR_GREATER_THAN ) return 1;
2163 /****************************************************************************
2164 * WCMD_setshow_sortenv
2166 * sort variables into order for display
2167 * Optionally only display those who start with a stub
2168 * returns the count displayed
2170 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2172 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2175 if (stub) stublen = strlenW(stub);
2177 /* count the number of strings, and the total length */
2179 len += (strlenW(&s[len]) + 1);
2183 /* add the strings to an array */
2184 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2188 for( i=1; i<count; i++ )
2189 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2191 /* sort the array */
2192 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2195 for( i=0; i<count; i++ ) {
2196 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2197 NORM_IGNORECASE | SORT_STRINGSORT,
2198 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2199 /* Don't display special internal variables */
2200 if (str[i][0] != '=') {
2201 WCMD_output_asis(str[i]);
2202 WCMD_output_asis(newline);
2209 return displayedcount;
2212 /****************************************************************************
2215 * Set/Show the environment variables
2218 void WCMD_setshow_env (WCHAR *s) {
2223 static const WCHAR parmP[] = {'/','P','\0'};
2225 if (param1[0] == 0x00 && quals[0] == 0x00) {
2226 env = GetEnvironmentStringsW();
2227 WCMD_setshow_sortenv( env, NULL );
2231 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2232 if (CompareStringW(LOCALE_USER_DEFAULT,
2233 NORM_IGNORECASE | SORT_STRINGSORT,
2234 s, 2, parmP, -1) == CSTR_EQUAL) {
2235 WCHAR string[MAXSTRING];
2239 while (*s && (*s==' ' || *s=='\t')) s++;
2241 WCMD_opt_s_strip_quotes(s);
2243 /* If no parameter, or no '=' sign, return an error */
2244 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2245 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2249 /* Output the prompt */
2251 if (strlenW(p) != 0) WCMD_output(p);
2253 /* Read the reply */
2254 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2256 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2257 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2258 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2259 wine_dbgstr_w(string));
2260 status = SetEnvironmentVariableW(s, string);
2267 WCMD_opt_s_strip_quotes(s);
2268 p = strchrW (s, '=');
2270 env = GetEnvironmentStringsW();
2271 if (WCMD_setshow_sortenv( env, s ) == 0) {
2272 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2279 if (strlenW(p) == 0) p = NULL;
2280 status = SetEnvironmentVariableW(s, p);
2281 gle = GetLastError();
2282 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2284 } else if ((!status)) WCMD_print_error();
2288 /****************************************************************************
2291 * Set/Show the path environment variable
2294 void WCMD_setshow_path (const WCHAR *command) {
2298 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2299 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2301 if (strlenW(param1) == 0) {
2302 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2304 WCMD_output_asis ( pathEqW);
2305 WCMD_output_asis ( string);
2306 WCMD_output_asis ( newline);
2309 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2313 if (*command == '=') command++; /* Skip leading '=' */
2314 status = SetEnvironmentVariableW(pathW, command);
2315 if (!status) WCMD_print_error();
2319 /****************************************************************************
2320 * WCMD_setshow_prompt
2322 * Set or show the command prompt.
2325 void WCMD_setshow_prompt (void) {
2328 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2330 if (strlenW(param1) == 0) {
2331 SetEnvironmentVariableW(promptW, NULL);
2335 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2336 if (strlenW(s) == 0) {
2337 SetEnvironmentVariableW(promptW, NULL);
2339 else SetEnvironmentVariableW(promptW, s);
2343 /****************************************************************************
2346 * Set/Show the system time
2347 * FIXME: Can't change time yet
2350 void WCMD_setshow_time (void) {
2352 WCHAR curtime[64], buffer[64];
2355 static const WCHAR parmT[] = {'/','T','\0'};
2357 if (strlenW(param1) == 0) {
2359 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2360 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2361 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2362 if (strstrW (quals, parmT) == NULL) {
2363 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2364 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2366 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2370 else WCMD_print_error ();
2373 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2377 /****************************************************************************
2380 * Shift batch parameters.
2381 * Optional /n says where to start shifting (n=0-8)
2384 void WCMD_shift (const WCHAR *command) {
2387 if (context != NULL) {
2388 WCHAR *pos = strchrW(command, '/');
2393 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2394 start = (*(pos+1) - '0');
2396 SetLastError(ERROR_INVALID_PARAMETER);
2401 WINE_TRACE("Shifting variables, starting at %d\n", start);
2402 for (i=start;i<=8;i++) {
2403 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2405 context -> shift_count[9] = context -> shift_count[9] + 1;
2410 /****************************************************************************
2413 * Set the console title
2415 void WCMD_title (const WCHAR *command) {
2416 SetConsoleTitleW(command);
2419 /****************************************************************************
2422 * Copy a file to standard output.
2425 void WCMD_type (WCHAR *command) {
2428 WCHAR *argN = command;
2429 BOOL writeHeaders = FALSE;
2431 if (param1[0] == 0x00) {
2432 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2436 if (param2[0] != 0x00) writeHeaders = TRUE;
2438 /* Loop through all args */
2441 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2449 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2450 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2451 FILE_ATTRIBUTE_NORMAL, NULL);
2452 if (h == INVALID_HANDLE_VALUE) {
2453 WCMD_print_error ();
2454 WCMD_output(WCMD_LoadMessage(WCMD_READFAIL), thisArg); /* should be _stderr */
2458 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2459 WCMD_output(fmt, thisArg);
2461 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2462 if (count == 0) break; /* ReadFile reports success on EOF! */
2464 WCMD_output_asis (buffer);
2471 /****************************************************************************
2474 * Output either a file or stdin to screen in pages
2477 void WCMD_more (WCHAR *command) {
2480 WCHAR *argN = command;
2482 WCHAR moreStrPage[100];
2485 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2486 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2487 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2488 ')',' ','-','-','\n','\0'};
2489 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2491 /* Prefix the NLS more with '-- ', then load the text */
2493 strcpyW(moreStr, moreStart);
2494 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2495 (sizeof(moreStr)/sizeof(WCHAR))-3);
2497 if (param1[0] == 0x00) {
2499 /* Wine implements pipes via temporary files, and hence stdin is
2500 effectively reading from the file. This means the prompts for
2501 more are satisfied by the next line from the input (file). To
2502 avoid this, ensure stdin is to the console */
2503 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2504 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2505 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2506 FILE_ATTRIBUTE_NORMAL, 0);
2507 WINE_TRACE("No parms - working probably in pipe mode\n");
2508 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2510 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2511 once you get in this bit unless due to a pipe, its going to end badly... */
2512 wsprintfW(moreStrPage, moreFmt, moreStr);
2514 WCMD_enter_paged_mode(moreStrPage);
2515 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2516 if (count == 0) break; /* ReadFile reports success on EOF! */
2518 WCMD_output_asis (buffer);
2520 WCMD_leave_paged_mode();
2522 /* Restore stdin to what it was */
2523 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2524 CloseHandle(hConIn);
2528 BOOL needsPause = FALSE;
2530 /* Loop through all args */
2531 WINE_TRACE("Parms supplied - working through each file\n");
2532 WCMD_enter_paged_mode(moreStrPage);
2535 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2543 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2544 WCMD_leave_paged_mode();
2545 WCMD_output_asis(moreStrPage);
2546 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2547 WCMD_enter_paged_mode(moreStrPage);
2551 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2552 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2553 FILE_ATTRIBUTE_NORMAL, NULL);
2554 if (h == INVALID_HANDLE_VALUE) {
2555 WCMD_print_error ();
2556 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2560 ULONG64 fileLen = 0;
2561 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2563 /* Get the file size */
2564 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2565 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2568 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2569 if (count == 0) break; /* ReadFile reports success on EOF! */
2573 /* Update % count (would be used in WCMD_output_asis as prompt) */
2574 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2576 WCMD_output_asis (buffer);
2582 WCMD_leave_paged_mode();
2586 /****************************************************************************
2589 * Display verify flag.
2590 * FIXME: We don't actually do anything with the verify flag other than toggle
2594 void WCMD_verify (const WCHAR *command) {
2598 count = strlenW(command);
2600 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2601 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2604 if (lstrcmpiW(command, onW) == 0) {
2608 else if (lstrcmpiW(command, offW) == 0) {
2609 verify_mode = FALSE;
2612 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2615 /****************************************************************************
2618 * Display version info.
2621 void WCMD_version (void) {
2623 WCMD_output (version_string);
2627 /****************************************************************************
2630 * Display volume information (set_label = FALSE)
2631 * Additionally set volume label (set_label = TRUE)
2632 * Returns 1 on success, 0 otherwise
2635 int WCMD_volume(BOOL set_label, const WCHAR *path)
2637 DWORD count, serial;
2638 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2641 if (strlenW(path) == 0) {
2642 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2644 WCMD_print_error ();
2647 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2648 &serial, NULL, NULL, NULL, 0);
2651 static const WCHAR fmt[] = {'%','s','\\','\0'};
2652 if ((path[1] != ':') || (strlenW(path) != 2)) {
2653 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2656 wsprintfW (curdir, fmt, path);
2657 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2662 WCMD_print_error ();
2665 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2666 curdir[0], label, HIWORD(serial), LOWORD(serial));
2668 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2669 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2671 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2672 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2674 if (strlenW(path) != 0) {
2675 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2678 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2684 /**************************************************************************
2687 * Exit either the process, or just this batch program
2691 void WCMD_exit (CMD_LIST **cmdList) {
2693 static const WCHAR parmB[] = {'/','B','\0'};
2694 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2696 if (context && lstrcmpiW(quals, parmB) == 0) {
2698 context -> skip_rest = TRUE;
2706 /*****************************************************************************
2709 * Lists or sets file associations (assoc = TRUE)
2710 * Lists or sets file types (assoc = FALSE)
2712 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2715 DWORD accessOptions = KEY_READ;
2717 LONG rc = ERROR_SUCCESS;
2718 WCHAR keyValue[MAXSTRING];
2719 DWORD valueLen = MAXSTRING;
2721 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2722 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2724 /* See if parameter includes '=' */
2726 newValue = strchrW(command, '=');
2727 if (newValue) accessOptions |= KEY_WRITE;
2729 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2730 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2731 accessOptions, &key) != ERROR_SUCCESS) {
2732 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2736 /* If no parameters then list all associations */
2737 if (*command == 0x00) {
2740 /* Enumerate all the keys */
2741 while (rc != ERROR_NO_MORE_ITEMS) {
2742 WCHAR keyName[MAXSTRING];
2745 /* Find the next value */
2746 nameLen = MAXSTRING;
2747 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2749 if (rc == ERROR_SUCCESS) {
2751 /* Only interested in extension ones if assoc, or others
2753 if ((keyName[0] == '.' && assoc) ||
2754 (!(keyName[0] == '.') && (!assoc)))
2756 WCHAR subkey[MAXSTRING];
2757 strcpyW(subkey, keyName);
2758 if (!assoc) strcatW(subkey, shOpCmdW);
2760 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2762 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2763 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2764 WCMD_output_asis(keyName);
2765 WCMD_output_asis(equalW);
2766 /* If no default value found, leave line empty after '=' */
2767 if (rc == ERROR_SUCCESS) {
2768 WCMD_output_asis(keyValue);
2770 WCMD_output_asis(newline);
2771 RegCloseKey(readKey);
2779 /* Parameter supplied - if no '=' on command line, its a query */
2780 if (newValue == NULL) {
2782 WCHAR subkey[MAXSTRING];
2784 /* Query terminates the parameter at the first space */
2785 strcpyW(keyValue, command);
2786 space = strchrW(keyValue, ' ');
2787 if (space) *space=0x00;
2789 /* Set up key name */
2790 strcpyW(subkey, keyValue);
2791 if (!assoc) strcatW(subkey, shOpCmdW);
2793 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2795 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2796 WCMD_output_asis(command);
2797 WCMD_output_asis(equalW);
2798 /* If no default value found, leave line empty after '=' */
2799 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2800 WCMD_output_asis(newline);
2801 RegCloseKey(readKey);
2804 WCHAR msgbuffer[MAXSTRING];
2805 WCHAR outbuffer[MAXSTRING];
2807 /* Load the translated 'File association not found' */
2809 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2811 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2813 wsprintfW(outbuffer, msgbuffer, keyValue);
2814 WCMD_output_asis_stderr(outbuffer);
2818 /* Not a query - its a set or clear of a value */
2821 WCHAR subkey[MAXSTRING];
2823 /* Get pointer to new value */
2827 /* Set up key name */
2828 strcpyW(subkey, command);
2829 if (!assoc) strcatW(subkey, shOpCmdW);
2831 /* If nothing after '=' then clear value - only valid for ASSOC */
2832 if (*newValue == 0x00) {
2834 if (assoc) rc = RegDeleteKeyW(key, command);
2835 if (assoc && rc == ERROR_SUCCESS) {
2836 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2838 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2843 WCHAR msgbuffer[MAXSTRING];
2844 WCHAR outbuffer[MAXSTRING];
2846 /* Load the translated 'File association not found' */
2848 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2849 sizeof(msgbuffer)/sizeof(WCHAR));
2851 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2852 sizeof(msgbuffer)/sizeof(WCHAR));
2854 wsprintfW(outbuffer, msgbuffer, keyValue);
2855 WCMD_output_asis_stderr(outbuffer);
2859 /* It really is a set value = contents */
2861 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2862 accessOptions, NULL, &readKey, NULL);
2863 if (rc == ERROR_SUCCESS) {
2864 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2866 sizeof(WCHAR) * (strlenW(newValue) + 1));
2867 RegCloseKey(readKey);
2870 if (rc != ERROR_SUCCESS) {
2874 WCMD_output_asis(command);
2875 WCMD_output_asis(equalW);
2876 WCMD_output_asis(newValue);
2877 WCMD_output_asis(newline);
2887 /****************************************************************************
2890 * Colors the terminal screen.
2893 void WCMD_color (void) {
2895 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2896 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2898 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2899 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
2903 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2909 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2914 /* Convert the color hex digits */
2915 if (param1[0] == 0x00) {
2916 color = defaultColor;
2918 color = strtoulW(param1, NULL, 16);
2921 /* Fail if fg == bg color */
2922 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2927 /* Set the current screen contents and ensure all future writes
2928 remain this color */
2929 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2930 SetConsoleTextAttribute(hStdOut, color);