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 struct env_stack *saved_environment;
49 struct env_stack *pushd_directories;
51 extern HINSTANCE hinst;
52 extern WCHAR inbuilt[][10];
53 extern int echo_mode, verify_mode, defaultColor;
54 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
55 extern BATCH_CONTEXT *context;
56 extern DWORD errorlevel;
58 static const WCHAR dotW[] = {'.','\0'};
59 static const WCHAR dotdotW[] = {'.','.','\0'};
60 static const WCHAR slashW[] = {'\\','\0'};
61 static const WCHAR starW[] = {'*','\0'};
62 static const WCHAR equalW[] = {'=','\0'};
63 static const WCHAR fslashW[] = {'/','\0'};
64 static const WCHAR onW[] = {'O','N','\0'};
65 static const WCHAR offW[] = {'O','F','F','\0'};
66 static const WCHAR parmY[] = {'/','Y','\0'};
67 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
68 static const WCHAR nullW[] = {'\0'};
70 /**************************************************************************
73 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
76 * Returns True if Y (or A) answer is selected
77 * If optionAll contains a pointer, ALL is allowed, and if answered
81 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
82 const BOOL *optionAll) {
84 WCHAR msgbuffer[MAXSTRING];
85 WCHAR Ybuffer[MAXSTRING];
86 WCHAR Nbuffer[MAXSTRING];
87 WCHAR Abuffer[MAXSTRING];
88 WCHAR answer[MAX_PATH] = {'\0'};
91 /* Load the translated 'Are you sure', plus valid answers */
92 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
93 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
94 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
95 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
97 /* Loop waiting on a Y or N */
98 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
99 static const WCHAR startBkt[] = {' ','(','\0'};
100 static const WCHAR endBkt[] = {')','?','\0'};
102 WCMD_output_asis (message);
104 WCMD_output_asis (msgbuffer);
106 WCMD_output_asis (startBkt);
107 WCMD_output_asis (Ybuffer);
108 WCMD_output_asis (fslashW);
109 WCMD_output_asis (Nbuffer);
111 WCMD_output_asis (fslashW);
112 WCMD_output_asis (Abuffer);
114 WCMD_output_asis (endBkt);
115 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
116 sizeof(answer)/sizeof(WCHAR), &count, NULL);
117 answer[0] = toupperW(answer[0]);
120 /* Return the answer */
121 return ((answer[0] == Ybuffer[0]) ||
122 (optionAll && (answer[0] == Abuffer[0])));
125 /****************************************************************************
128 * Clear the terminal screen.
131 void WCMD_clear_screen (void) {
133 /* Emulate by filling the screen from the top left to bottom right with
134 spaces, then moving the cursor to the top left afterwards */
135 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
136 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
138 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
143 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
147 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
148 SetConsoleCursorPosition(hStdOut, topLeft);
152 /****************************************************************************
155 * Change the default i/o device (ie redirect STDin/STDout).
158 void WCMD_change_tty (void) {
160 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
164 /****************************************************************************
169 void WCMD_choice (const WCHAR * command) {
171 static const WCHAR bellW[] = {7,0};
172 static const WCHAR commaW[] = {',',0};
173 static const WCHAR bracket_open[] = {'[',0};
174 static const WCHAR bracket_close[] = {']','?',0};
179 WCHAR *my_command = NULL;
180 WCHAR opt_default = 0;
181 DWORD opt_timeout = 0;
188 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
191 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
195 ptr = WCMD_skip_leading_spaces(my_command);
196 while (*ptr == '/') {
197 switch (toupperW(ptr[1])) {
200 /* the colon is optional */
204 if (!*ptr || isspaceW(*ptr)) {
205 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
206 HeapFree(GetProcessHeap(), 0, my_command);
210 /* remember the allowed keys (overwrite previous /C option) */
212 while (*ptr && (!isspaceW(*ptr)))
216 /* terminate allowed chars */
218 ptr = WCMD_skip_leading_spaces(&ptr[1]);
220 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
225 ptr = WCMD_skip_leading_spaces(&ptr[2]);
230 ptr = WCMD_skip_leading_spaces(&ptr[2]);
235 /* the colon is optional */
239 opt_default = *ptr++;
241 if (!opt_default || (*ptr != ',')) {
242 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
243 HeapFree(GetProcessHeap(), 0, my_command);
249 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
255 opt_timeout = atoiW(answer);
257 ptr = WCMD_skip_leading_spaces(ptr);
261 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
262 HeapFree(GetProcessHeap(), 0, my_command);
268 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
271 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
273 /* use default keys, when needed: localized versions of "Y"es and "No" */
275 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
276 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
281 /* print the question, when needed */
283 WCMD_output_asis(ptr);
287 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
291 /* print a list of all allowed answers inside brackets */
292 WCMD_output_asis(bracket_open);
295 while ((answer[0] = *ptr++)) {
296 WCMD_output_asis(answer);
298 WCMD_output_asis(commaW);
300 WCMD_output_asis(bracket_close);
305 /* FIXME: Add support for option /T */
306 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count, NULL);
309 answer[0] = toupperW(answer[0]);
311 ptr = strchrW(opt_c, answer[0]);
313 WCMD_output_asis(answer);
314 WCMD_output(newline);
316 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
318 errorlevel = (ptr - opt_c) + 1;
319 WINE_TRACE("answer: %d\n", errorlevel);
320 HeapFree(GetProcessHeap(), 0, my_command);
325 /* key not allowed: play the bell */
326 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
327 WCMD_output_asis(bellW);
332 /****************************************************************************
335 * Copy a file or wildcarded set.
336 * FIXME: Add support for a+b+c type syntax
339 void WCMD_copy (void) {
344 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
346 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
347 BOOL copyToDir = FALSE;
348 WCHAR srcspec[MAX_PATH];
352 WCHAR fname[MAX_PATH];
355 if (param1[0] == 0x00) {
356 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
360 /* Convert source into full spec */
361 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
362 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
363 if (srcpath[strlenW(srcpath) - 1] == '\\')
364 srcpath[strlenW(srcpath) - 1] = '\0';
366 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
367 attribs = GetFileAttributesW(srcpath);
371 strcpyW(srcspec, srcpath);
373 /* If a directory, then add \* on the end when searching */
374 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
375 strcatW(srcpath, slashW);
376 strcatW(srcspec, slashW);
377 strcatW(srcspec, starW);
379 WCMD_splitpath(srcpath, drive, dir, fname, ext);
380 strcpyW(srcpath, drive);
381 strcatW(srcpath, dir);
384 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
386 /* If no destination supplied, assume current directory */
387 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
388 if (param2[0] == 0x00) {
389 strcpyW(param2, dotW);
392 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
393 if (outpath[strlenW(outpath) - 1] == '\\')
394 outpath[strlenW(outpath) - 1] = '\0';
395 attribs = GetFileAttributesW(outpath);
396 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
397 strcatW (outpath, slashW);
400 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
401 wine_dbgstr_w(outpath), copyToDir);
403 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
404 if (strstrW (quals, parmNoY))
406 else if (strstrW (quals, parmY))
409 /* By default, we will force the overwrite in batch mode and ask for
410 * confirmation in interactive mode. */
413 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
414 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
415 * default behavior. */
416 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
417 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
418 if (!lstrcmpiW (copycmd, parmY))
420 else if (!lstrcmpiW (copycmd, parmNoY))
425 /* Loop through all source files */
426 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
427 hff = FindFirstFileW(srcspec, &fd);
428 if (hff != INVALID_HANDLE_VALUE) {
430 WCHAR outname[MAX_PATH];
431 WCHAR srcname[MAX_PATH];
432 BOOL overwrite = force;
434 /* Destination is either supplied filename, or source name in
435 supplied destination directory */
436 strcpyW(outname, outpath);
437 if (copyToDir) strcatW(outname, fd.cFileName);
438 strcpyW(srcname, srcpath);
439 strcatW(srcname, fd.cFileName);
441 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
442 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
444 /* Skip . and .., and directories */
445 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
447 WINE_TRACE("Skipping directories\n");
450 /* Prompt before overwriting */
451 else if (!overwrite) {
452 attribs = GetFileAttributesW(outname);
453 if (attribs != INVALID_FILE_ATTRIBUTES) {
454 WCHAR buffer[MAXSTRING];
455 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
456 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
458 else overwrite = TRUE;
461 /* Do the copy as appropriate */
463 status = CopyFileW(srcname, outname, FALSE);
464 if (!status) WCMD_print_error ();
467 } while (FindNextFileW(hff, &fd) != 0);
470 status = ERROR_FILE_NOT_FOUND;
475 /****************************************************************************
478 * Create a directory (and, if needed, any intermediate directories).
480 * Modifies its argument by replacing slashes temporarily with nulls.
483 static BOOL create_full_path(WCHAR* path)
487 /* don't mess with drive letter portion of path, if any */
492 /* Strip trailing slashes. */
493 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
496 /* Step through path, creating intermediate directories as needed. */
497 /* First component includes drive letter, if any. */
501 /* Skip to end of component */
502 while (*p == '\\') p++;
503 while (*p && *p != '\\') p++;
505 /* path is now the original full path */
506 return CreateDirectoryW(path, NULL);
508 /* Truncate path, create intermediate directory, and restore path */
510 rv = CreateDirectoryW(path, NULL);
512 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
519 void WCMD_create_dir (WCHAR *command) {
521 WCHAR *argN = command;
523 if (param1[0] == 0x00) {
524 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
527 /* Loop through all args */
529 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN);
531 if (!create_full_path(thisArg)) {
538 /* Parse the /A options given by the user on the commandline
539 * into a bitmask of wanted attributes (*wantSet),
540 * and a bitmask of unwanted attributes (*wantClear).
542 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
543 static const WCHAR parmA[] = {'/','A','\0'};
546 /* both are strictly 'out' parameters */
550 /* For each /A argument */
551 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
555 /* Skip optional : */
558 /* For each of the attribute specifier chars to this /A option */
559 for (; *p != 0 && *p != '/'; p++) {
568 /* Convert the attribute specifier to a bit in one of the masks */
570 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
571 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
572 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
573 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
575 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
585 /* If filename part of parameter is * or *.*,
586 * and neither /Q nor /P options were given,
587 * prompt the user whether to proceed.
588 * Returns FALSE if user says no, TRUE otherwise.
589 * *pPrompted is set to TRUE if the user is prompted.
590 * (If /P supplied, del will prompt for individual files later.)
592 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
593 static const WCHAR parmP[] = {'/','P','\0'};
594 static const WCHAR parmQ[] = {'/','Q','\0'};
596 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
597 static const WCHAR anyExt[]= {'.','*','\0'};
600 WCHAR fname[MAX_PATH];
602 WCHAR fpath[MAX_PATH];
604 /* Convert path into actual directory spec */
605 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
606 WCMD_splitpath(fpath, drive, dir, fname, ext);
608 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
609 if ((strcmpW(fname, starW) == 0) &&
610 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
612 WCHAR question[MAXSTRING];
613 static const WCHAR fmt[] = {'%','s',' ','\0'};
615 /* Caller uses this to suppress "file not found" warning later */
618 /* Ask for confirmation */
619 wsprintfW(question, fmt, fpath);
620 return WCMD_ask_confirm(question, TRUE, NULL);
623 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
627 /* Helper function for WCMD_delete().
628 * Deletes a single file, directory, or wildcard.
629 * If /S was given, does it recursively.
630 * Returns TRUE if a file was deleted.
632 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
634 static const WCHAR parmP[] = {'/','P','\0'};
635 static const WCHAR parmS[] = {'/','S','\0'};
636 static const WCHAR parmF[] = {'/','F','\0'};
638 DWORD unwanted_attrs;
640 WCHAR argCopy[MAX_PATH];
643 WCHAR fpath[MAX_PATH];
645 BOOL handleParm = TRUE;
647 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
649 strcpyW(argCopy, thisArg);
650 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
651 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
653 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
654 /* Skip this arg if user declines to delete *.* */
658 /* First, try to delete in the current directory */
659 hff = FindFirstFileW(argCopy, &fd);
660 if (hff == INVALID_HANDLE_VALUE) {
666 /* Support del <dirname> by just deleting all files dirname\* */
668 && (strchrW(argCopy,'*') == NULL)
669 && (strchrW(argCopy,'?') == NULL)
670 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
672 WCHAR modifiedParm[MAX_PATH];
673 static const WCHAR slashStar[] = {'\\','*','\0'};
675 strcpyW(modifiedParm, argCopy);
676 strcatW(modifiedParm, slashStar);
679 WCMD_delete_one(modifiedParm);
681 } else if (handleParm) {
683 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
684 strcpyW (fpath, argCopy);
686 p = strrchrW (fpath, '\\');
689 strcatW (fpath, fd.cFileName);
691 else strcpyW (fpath, fd.cFileName);
692 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
695 /* Handle attribute matching (/A) */
696 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
697 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
699 /* /P means prompt for each file */
700 if (ok && strstrW (quals, parmP) != NULL) {
701 WCHAR question[MAXSTRING];
703 /* Ask for confirmation */
704 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
705 ok = WCMD_ask_confirm(question, FALSE, NULL);
708 /* Only proceed if ok to */
711 /* If file is read only, and /A:r or /F supplied, delete it */
712 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
713 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
714 strstrW (quals, parmF) != NULL)) {
715 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
718 /* Now do the delete */
719 if (!DeleteFileW(fpath)) WCMD_print_error ();
723 } while (FindNextFileW(hff, &fd) != 0);
727 /* Now recurse into all subdirectories handling the parameter in the same way */
728 if (strstrW (quals, parmS) != NULL) {
730 WCHAR thisDir[MAX_PATH];
735 WCHAR fname[MAX_PATH];
738 /* Convert path into actual directory spec */
739 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
740 WCMD_splitpath(thisDir, drive, dir, fname, ext);
742 strcpyW(thisDir, drive);
743 strcatW(thisDir, dir);
744 cPos = strlenW(thisDir);
746 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
748 /* Append '*' to the directory */
750 thisDir[cPos+1] = 0x00;
752 hff = FindFirstFileW(thisDir, &fd);
754 /* Remove residual '*' */
755 thisDir[cPos] = 0x00;
757 if (hff != INVALID_HANDLE_VALUE) {
758 DIRECTORY_STACK *allDirs = NULL;
759 DIRECTORY_STACK *lastEntry = NULL;
762 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
763 (strcmpW(fd.cFileName, dotdotW) != 0) &&
764 (strcmpW(fd.cFileName, dotW) != 0)) {
766 DIRECTORY_STACK *nextDir;
767 WCHAR subParm[MAX_PATH];
769 /* Work out search parameter in sub dir */
770 strcpyW (subParm, thisDir);
771 strcatW (subParm, fd.cFileName);
772 strcatW (subParm, slashW);
773 strcatW (subParm, fname);
774 strcatW (subParm, ext);
775 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
777 /* Allocate memory, add to list */
778 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
779 if (allDirs == NULL) allDirs = nextDir;
780 if (lastEntry != NULL) lastEntry->next = nextDir;
782 nextDir->next = NULL;
783 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
784 (strlenW(subParm)+1) * sizeof(WCHAR));
785 strcpyW(nextDir->dirName, subParm);
787 } while (FindNextFileW(hff, &fd) != 0);
790 /* Go through each subdir doing the delete */
791 while (allDirs != NULL) {
792 DIRECTORY_STACK *tempDir;
794 tempDir = allDirs->next;
795 found |= WCMD_delete_one (allDirs->dirName);
797 HeapFree(GetProcessHeap(),0,allDirs->dirName);
798 HeapFree(GetProcessHeap(),0,allDirs);
807 /****************************************************************************
810 * Delete a file or wildcarded set.
813 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
814 * - Each set is a pattern, eg /ahr /as-r means
815 * readonly+hidden OR nonreadonly system files
816 * - The '-' applies to a single field, ie /a:-hr means read only
820 BOOL WCMD_delete (WCHAR *command) {
823 BOOL argsProcessed = FALSE;
824 BOOL foundAny = FALSE;
828 for (argno=0; ; argno++) {
833 thisArg = WCMD_parameter (command, argno, &argN);
835 break; /* no more parameters */
837 continue; /* skip options */
839 argsProcessed = TRUE;
840 found = WCMD_delete_one(thisArg);
843 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
848 /* Handle no valid args */
850 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
855 /****************************************************************************
858 * Echo input to the screen (or not). We don't try to emulate the bugs
859 * in DOS (try typing "ECHO ON AGAIN" for an example).
862 void WCMD_echo (const WCHAR *command) {
865 const WCHAR *origcommand = command;
867 if (command[0]==' ' || command[0]=='\t' || command[0]=='.' || command[0]==':')
869 count = strlenW(command);
870 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':') {
871 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
872 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
875 if (lstrcmpiW(command, onW) == 0) {
879 if (lstrcmpiW(command, offW) == 0) {
883 WCMD_output_asis (command);
884 WCMD_output (newline);
888 /**************************************************************************
891 * Batch file loop processing.
893 * On entry: cmdList contains the syntax up to the set
894 * next cmdList and all in that bracket contain the set data
895 * next cmdlist contains the DO cmd
896 * following that is either brackets or && entries (as per if)
900 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
905 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
906 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
907 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
913 BOOL expandDirs = FALSE;
914 BOOL useNumbers = FALSE;
915 BOOL doFileset = FALSE;
916 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
918 CMD_LIST *thisCmdStart;
921 /* Handle optional qualifiers (multiple are allowed) */
922 while (*curPos && *curPos == '/') {
923 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
925 switch (toupperW(*curPos)) {
926 case 'D': curPos++; expandDirs = TRUE; break;
927 case 'L': curPos++; useNumbers = TRUE; break;
929 /* Recursive is special case - /R can have an optional path following it */
930 /* filenamesets are another special case - /F can have an optional options following it */
934 BOOL isRecursive = (*curPos == 'R');
939 /* Skip whitespace */
941 while (*curPos && *curPos==' ') curPos++;
943 /* Next parm is either qualifier, path/options or variable -
944 only care about it if it is the path/options */
945 if (*curPos && *curPos != '/' && *curPos != '%') {
946 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
947 else WINE_FIXME("/F needs to handle options\n");
952 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
956 /* Skip whitespace between qualifiers */
957 while (*curPos && *curPos==' ') curPos++;
960 /* Skip whitespace before variable */
961 while (*curPos && *curPos==' ') curPos++;
963 /* Ensure line continues with variable */
964 if (!*curPos || *curPos != '%') {
965 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
969 /* Variable should follow */
971 while (curPos[i] && curPos[i]!=' ') i++;
972 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
974 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
977 /* Skip whitespace before IN */
978 while (*curPos && *curPos==' ') curPos++;
980 /* Ensure line continues with IN */
982 || CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
983 curPos, 3, inW, -1) != CSTR_EQUAL) {
984 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
988 /* Save away where the set of data starts and the variable */
989 thisDepth = (*cmdList)->bracketDepth;
990 *cmdList = (*cmdList)->nextcommand;
991 setStart = (*cmdList);
993 /* Skip until the close bracket */
994 WINE_TRACE("Searching %p as the set\n", *cmdList);
996 (*cmdList)->command != NULL &&
997 (*cmdList)->bracketDepth > thisDepth) {
998 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
999 *cmdList = (*cmdList)->nextcommand;
1002 /* Skip the close bracket, if there is one */
1003 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1005 /* Syntax error if missing close bracket, or nothing following it
1006 and once we have the complete set, we expect a DO */
1007 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
1008 if ((*cmdList == NULL) ||
1009 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1010 (*cmdList)->command, 3, doW, -1) != CSTR_EQUAL)) {
1011 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1015 /* Save away the starting position for the commands (and offset for the
1017 cmdStart = *cmdList;
1019 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1023 /* Loop through all set entries */
1025 thisSet->command != NULL &&
1026 thisSet->bracketDepth >= thisDepth) {
1028 /* Loop through all entries on the same line */
1032 WINE_TRACE("Processing for set %p\n", thisSet);
1034 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
1037 * If the parameter within the set has a wildcard then search for matching files
1038 * otherwise do a literal substitution.
1040 static const WCHAR wildcards[] = {'*','?','\0'};
1041 thisCmdStart = cmdStart;
1044 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1046 if (!useNumbers && !doFileset) {
1047 if (strpbrkW (item, wildcards)) {
1048 hff = FindFirstFileW(item, &fd);
1049 if (hff != INVALID_HANDLE_VALUE) {
1051 BOOL isDirectory = FALSE;
1053 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1055 /* Handle as files or dirs appropriately, but ignore . and .. */
1056 if (isDirectory == expandDirs &&
1057 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1058 (strcmpW(fd.cFileName, dotW) != 0))
1060 thisCmdStart = cmdStart;
1061 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1062 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1063 fd.cFileName, FALSE, TRUE);
1066 } while (FindNextFileW(hff, &fd) != 0);
1070 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1073 } else if (useNumbers) {
1074 /* Convert the first 3 numbers to signed longs and save */
1075 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1076 /* else ignore them! */
1078 /* Filesets - either a list of files, or a command to run and parse the output */
1079 } else if (doFileset && *itemStart != '"') {
1082 WCHAR temp_file[MAX_PATH];
1084 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1085 wine_dbgstr_w(item));
1087 /* If backquote or single quote, we need to launch that command
1088 and parse the results - use a temporary file */
1089 if (*itemStart == '`' || *itemStart == '\'') {
1091 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1092 static const WCHAR redirOut[] = {'>','%','s','\0'};
1093 static const WCHAR cmdW[] = {'C','M','D','\0'};
1095 /* Remove trailing character */
1096 itemStart[strlenW(itemStart)-1] = 0x00;
1098 /* Get temp filename */
1099 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1100 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1102 /* Execute program and redirect output */
1103 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1104 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1106 /* Open the file, read line by line and process */
1107 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1108 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1111 /* Open the file, read line by line and process */
1112 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1113 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1116 /* Process the input file */
1117 if (input == INVALID_HANDLE_VALUE) {
1118 WCMD_print_error ();
1119 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1121 return; /* FOR loop aborts at first failure here */
1125 WCHAR buffer[MAXSTRING] = {'\0'};
1126 WCHAR *where, *parm;
1128 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1130 /* Skip blank lines*/
1131 parm = WCMD_parameter (buffer, 0, &where);
1132 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1133 wine_dbgstr_w(buffer));
1136 /* FIXME: The following should be moved into its own routine and
1137 reused for the string literal parsing below */
1138 thisCmdStart = cmdStart;
1139 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1140 cmdEnd = thisCmdStart;
1146 CloseHandle (input);
1149 /* Delete the temporary file */
1150 if (*itemStart == '`' || *itemStart == '\'') {
1151 DeleteFileW(temp_file);
1154 /* Filesets - A string literal */
1155 } else if (doFileset && *itemStart == '"') {
1156 WCHAR buffer[MAXSTRING] = {'\0'};
1157 WCHAR *where, *parm;
1159 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1160 strcpyW(buffer, item);
1161 parm = WCMD_parameter (buffer, 0, &where);
1162 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1163 wine_dbgstr_w(buffer));
1166 /* FIXME: The following should be moved into its own routine and
1167 reused for the string literal parsing below */
1168 thisCmdStart = cmdStart;
1169 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1170 cmdEnd = thisCmdStart;
1174 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1175 cmdEnd = thisCmdStart;
1179 /* Move onto the next set line */
1180 thisSet = thisSet->nextcommand;
1183 /* If /L is provided, now run the for loop */
1186 static const WCHAR fmt[] = {'%','d','\0'};
1188 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1189 numbers[0], numbers[2], numbers[1]);
1191 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1194 sprintfW(thisNum, fmt, i);
1195 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1197 thisCmdStart = cmdStart;
1198 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1199 cmdEnd = thisCmdStart;
1203 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1204 all processing, OR it should be pointing to the end of && processing OR
1205 it should be pointing at the NULL end of bracket for the DO. The return
1206 value needs to be the NEXT command to execute, which it either is, or
1207 we need to step over the closing bracket */
1209 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1213 /*****************************************************************************
1216 * Execute a command, and any && or bracketed follow on to the command. The
1217 * first command to be executed may not be at the front of the
1218 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1220 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1221 const WCHAR *variable, const WCHAR *value,
1222 BOOL isIF, BOOL conditionTRUE) {
1224 CMD_LIST *curPosition = *cmdList;
1225 int myDepth = (*cmdList)->bracketDepth;
1227 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1228 cmdList, wine_dbgstr_w(firstcmd),
1229 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1232 /* Skip leading whitespace between condition and the command */
1233 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1235 /* Process the first command, if there is one */
1236 if (conditionTRUE && firstcmd && *firstcmd) {
1237 WCHAR *command = WCMD_strdupW(firstcmd);
1238 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1239 HeapFree(GetProcessHeap(), 0, command);
1243 /* If it didn't move the position, step to next command */
1244 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1246 /* Process any other parts of the command */
1248 BOOL processThese = TRUE;
1250 if (isIF) processThese = conditionTRUE;
1253 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1255 /* execute all appropriate commands */
1256 curPosition = *cmdList;
1258 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1260 (*cmdList)->prevDelim,
1261 (*cmdList)->bracketDepth, myDepth);
1263 /* Execute any statements appended to the line */
1264 /* FIXME: Only if previous call worked for && or failed for || */
1265 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1266 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1267 if (processThese && (*cmdList)->command) {
1268 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1271 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1273 /* Execute any appended to the statement with (...) */
1274 } else if ((*cmdList)->bracketDepth > myDepth) {
1276 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1277 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1279 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1281 /* End of the command - does 'ELSE ' follow as the next command? */
1283 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1284 NORM_IGNORECASE | SORT_STRINGSORT,
1285 (*cmdList)->command, 5, ifElse, -1) == CSTR_EQUAL) {
1287 /* Swap between if and else processing */
1288 processThese = !processThese;
1290 /* Process the ELSE part */
1292 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1294 /* Skip leading whitespace between condition and the command */
1295 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1297 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1300 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1302 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1311 /**************************************************************************
1314 * Simple on-line help. Help text is stored in the resource file.
1317 void WCMD_give_help (const WCHAR *command) {
1321 command = WCMD_skip_leading_spaces((WCHAR*) command);
1322 if (strlenW(command) == 0) {
1323 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1326 for (i=0; i<=WCMD_EXIT; i++) {
1327 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1328 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1329 WCMD_output_asis (WCMD_LoadMessage(i));
1333 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1338 /****************************************************************************
1341 * Batch file jump instruction. Not the most efficient algorithm ;-)
1342 * Prints error message if the specified label cannot be found - the file pointer is
1343 * then at EOF, effectively stopping the batch file.
1344 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1347 void WCMD_goto (CMD_LIST **cmdList) {
1349 WCHAR string[MAX_PATH];
1350 WCHAR current[MAX_PATH];
1352 /* Do not process any more parts of a processed multipart or multilines command */
1353 if (cmdList) *cmdList = NULL;
1355 if (param1[0] == 0x00) {
1356 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1359 if (context != NULL) {
1360 WCHAR *paramStart = param1, *str;
1361 static const WCHAR eofW[] = {':','e','o','f','\0'};
1363 /* Handle special :EOF label */
1364 if (lstrcmpiW (eofW, param1) == 0) {
1365 context -> skip_rest = TRUE;
1369 /* Support goto :label as well as goto label */
1370 if (*paramStart == ':') paramStart++;
1372 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1373 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1375 while (isspaceW (*str)) str++;
1379 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1382 /* ignore space at the end */
1384 if (lstrcmpiW (current, paramStart) == 0) return;
1387 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1392 /*****************************************************************************
1395 * Push a directory onto the stack
1398 void WCMD_pushd (WCHAR *command) {
1399 struct env_stack *curdir;
1401 static const WCHAR parmD[] = {'/','D','\0'};
1403 if (strchrW(command, '/') != NULL) {
1404 SetLastError(ERROR_INVALID_PARAMETER);
1409 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1410 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1411 if( !curdir || !thisdir ) {
1414 WINE_ERR ("out of memory\n");
1418 /* Change directory using CD code with /D parameter */
1419 strcpyW(quals, parmD);
1420 GetCurrentDirectoryW (1024, thisdir);
1422 WCMD_setshow_default(command);
1428 curdir -> next = pushd_directories;
1429 curdir -> strings = thisdir;
1430 if (pushd_directories == NULL) {
1431 curdir -> u.stackdepth = 1;
1433 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1435 pushd_directories = curdir;
1440 /*****************************************************************************
1443 * Pop a directory from the stack
1446 void WCMD_popd (void) {
1447 struct env_stack *temp = pushd_directories;
1449 if (!pushd_directories)
1452 /* pop the old environment from the stack, and make it the current dir */
1453 pushd_directories = temp->next;
1454 SetCurrentDirectoryW(temp->strings);
1455 LocalFree (temp->strings);
1459 /****************************************************************************
1462 * Batch file conditional.
1464 * On entry, cmdlist will point to command containing the IF, and optionally
1465 * the first command to execute (if brackets not found)
1466 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1467 * If ('s were found, execute all within that bracket
1468 * Command may optionally be followed by an ELSE - need to skip instructions
1469 * in the else using the same logic
1471 * FIXME: Much more syntax checking needed!
1474 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1476 int negate = 0, test = 0;
1477 WCHAR condition[MAX_PATH], *command, *s;
1478 static const WCHAR notW[] = {'n','o','t','\0'};
1479 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1480 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1481 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1482 static const WCHAR eqeqW[] = {'=','=','\0'};
1483 static const WCHAR parmI[] = {'/','I','\0'};
1485 if (!lstrcmpiW (param1, notW)) {
1487 strcpyW (condition, param2);
1490 strcpyW (condition, param1);
1492 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1494 if (!lstrcmpiW (condition, errlvlW)) {
1495 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1496 WCMD_parameter (p, 2+negate, &command);
1498 else if (!lstrcmpiW (condition, existW)) {
1499 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1502 WCMD_parameter (p, 2+negate, &command);
1504 else if (!lstrcmpiW (condition, defdW)) {
1505 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1508 WCMD_parameter (p, 2+negate, &command);
1510 else if ((s = strstrW (p, eqeqW))) {
1512 if (strstrW (quals, parmI) == NULL) {
1513 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1516 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1518 WCMD_parameter (s, 1, &command);
1521 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1525 /* Process rest of IF statement which is on the same line
1526 Note: This may process all or some of the cmdList (eg a GOTO) */
1527 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1530 /****************************************************************************
1533 * Move a file, directory tree or wildcarded set of files.
1536 void WCMD_move (void) {
1539 WIN32_FIND_DATAW fd;
1541 WCHAR input[MAX_PATH];
1542 WCHAR output[MAX_PATH];
1544 WCHAR dir[MAX_PATH];
1545 WCHAR fname[MAX_PATH];
1546 WCHAR ext[MAX_PATH];
1548 if (param1[0] == 0x00) {
1549 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1553 /* If no destination supplied, assume current directory */
1554 if (param2[0] == 0x00) {
1555 strcpyW(param2, dotW);
1558 /* If 2nd parm is directory, then use original filename */
1559 /* Convert partial path to full path */
1560 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1561 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1562 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1563 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1565 /* Split into components */
1566 WCMD_splitpath(input, drive, dir, fname, ext);
1568 hff = FindFirstFileW(input, &fd);
1569 while (hff != INVALID_HANDLE_VALUE) {
1570 WCHAR dest[MAX_PATH];
1571 WCHAR src[MAX_PATH];
1574 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1576 /* Build src & dest name */
1577 strcpyW(src, drive);
1580 /* See if dest is an existing directory */
1581 attribs = GetFileAttributesW(output);
1582 if (attribs != INVALID_FILE_ATTRIBUTES &&
1583 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1584 strcpyW(dest, output);
1585 strcatW(dest, slashW);
1586 strcatW(dest, fd.cFileName);
1588 strcpyW(dest, output);
1591 strcatW(src, fd.cFileName);
1593 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1594 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1596 /* Check if file is read only, otherwise move it */
1597 attribs = GetFileAttributesW(src);
1598 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1599 (attribs & FILE_ATTRIBUTE_READONLY)) {
1600 SetLastError(ERROR_ACCESS_DENIED);
1605 /* If destination exists, prompt unless /Y supplied */
1606 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1608 WCHAR copycmd[MAXSTRING];
1611 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1612 if (strstrW (quals, parmNoY))
1614 else if (strstrW (quals, parmY))
1617 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1618 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1619 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1620 && ! lstrcmpiW (copycmd, parmY));
1623 /* Prompt if overwriting */
1625 WCHAR question[MAXSTRING];
1628 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1630 /* Ask for confirmation */
1631 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1632 ok = WCMD_ask_confirm(question, FALSE, NULL);
1634 /* So delete the destination prior to the move */
1636 if (!DeleteFileW(dest)) {
1637 WCMD_print_error ();
1646 status = MoveFileW(src, dest);
1648 status = 1; /* Anything other than 0 to prevent error msg below */
1653 WCMD_print_error ();
1657 /* Step on to next match */
1658 if (FindNextFileW(hff, &fd) == 0) {
1660 hff = INVALID_HANDLE_VALUE;
1666 /****************************************************************************
1669 * Wait for keyboard input.
1672 void WCMD_pause (void) {
1677 WCMD_output (anykey);
1678 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1679 sizeof(string)/sizeof(WCHAR), &count, NULL);
1682 /****************************************************************************
1685 * Delete a directory.
1688 void WCMD_remove_dir (WCHAR *command) {
1691 int argsProcessed = 0;
1692 WCHAR *argN = command;
1693 static const WCHAR parmS[] = {'/','S','\0'};
1694 static const WCHAR parmQ[] = {'/','Q','\0'};
1696 /* Loop through all args */
1698 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1699 if (argN && argN[0] != '/') {
1700 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1701 wine_dbgstr_w(quals));
1704 /* If subdirectory search not supplied, just try to remove
1705 and report error if it fails (eg if it contains a file) */
1706 if (strstrW (quals, parmS) == NULL) {
1707 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1709 /* Otherwise use ShFileOp to recursively remove a directory */
1712 SHFILEOPSTRUCTW lpDir;
1715 if (strstrW (quals, parmQ) == NULL) {
1717 WCHAR question[MAXSTRING];
1718 static const WCHAR fmt[] = {'%','s',' ','\0'};
1720 /* Ask for confirmation */
1721 wsprintfW(question, fmt, thisArg);
1722 ok = WCMD_ask_confirm(question, TRUE, NULL);
1724 /* Abort if answer is 'N' */
1731 lpDir.pFrom = thisArg;
1732 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1733 lpDir.wFunc = FO_DELETE;
1734 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1739 /* Handle no valid args */
1740 if (argsProcessed == 0) {
1741 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1747 /****************************************************************************
1753 void WCMD_rename (void) {
1757 WIN32_FIND_DATAW fd;
1758 WCHAR input[MAX_PATH];
1759 WCHAR *dotDst = NULL;
1761 WCHAR dir[MAX_PATH];
1762 WCHAR fname[MAX_PATH];
1763 WCHAR ext[MAX_PATH];
1768 /* Must be at least two args */
1769 if (param1[0] == 0x00 || param2[0] == 0x00) {
1770 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1775 /* Destination cannot contain a drive letter or directory separator */
1776 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1777 SetLastError(ERROR_INVALID_PARAMETER);
1783 /* Convert partial path to full path */
1784 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1785 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1786 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1787 dotDst = strchrW(param2, '.');
1789 /* Split into components */
1790 WCMD_splitpath(input, drive, dir, fname, ext);
1792 hff = FindFirstFileW(input, &fd);
1793 while (hff != INVALID_HANDLE_VALUE) {
1794 WCHAR dest[MAX_PATH];
1795 WCHAR src[MAX_PATH];
1796 WCHAR *dotSrc = NULL;
1799 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1801 /* FIXME: If dest name or extension is *, replace with filename/ext
1802 part otherwise use supplied name. This supports:
1804 ren jim.* fred.* etc
1805 However, windows has a more complex algorithm supporting eg
1806 ?'s and *'s mid name */
1807 dotSrc = strchrW(fd.cFileName, '.');
1809 /* Build src & dest name */
1810 strcpyW(src, drive);
1813 dirLen = strlenW(src);
1814 strcatW(src, fd.cFileName);
1817 if (param2[0] == '*') {
1818 strcatW(dest, fd.cFileName);
1819 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1821 strcatW(dest, param2);
1822 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1825 /* Build Extension */
1826 if (dotDst && (*(dotDst+1)=='*')) {
1827 if (dotSrc) strcatW(dest, dotSrc);
1828 } else if (dotDst) {
1829 if (dotDst) strcatW(dest, dotDst);
1832 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1833 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1835 /* Check if file is read only, otherwise move it */
1836 attribs = GetFileAttributesW(src);
1837 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1838 (attribs & FILE_ATTRIBUTE_READONLY)) {
1839 SetLastError(ERROR_ACCESS_DENIED);
1842 status = MoveFileW(src, dest);
1846 WCMD_print_error ();
1850 /* Step on to next match */
1851 if (FindNextFileW(hff, &fd) == 0) {
1853 hff = INVALID_HANDLE_VALUE;
1859 /*****************************************************************************
1862 * Make a copy of the environment.
1864 static WCHAR *WCMD_dupenv( const WCHAR *env )
1874 len += (strlenW(&env[len]) + 1);
1876 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1879 WINE_ERR("out of memory\n");
1882 memcpy (env_copy, env, len*sizeof (WCHAR));
1888 /*****************************************************************************
1891 * setlocal pushes the environment onto a stack
1892 * Save the environment as unicode so we don't screw anything up.
1894 void WCMD_setlocal (const WCHAR *s) {
1896 struct env_stack *env_copy;
1897 WCHAR cwd[MAX_PATH];
1899 /* DISABLEEXTENSIONS ignored */
1901 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1904 WINE_ERR ("out of memory\n");
1908 env = GetEnvironmentStringsW ();
1910 env_copy->strings = WCMD_dupenv (env);
1911 if (env_copy->strings)
1913 env_copy->next = saved_environment;
1914 saved_environment = env_copy;
1916 /* Save the current drive letter */
1917 GetCurrentDirectoryW(MAX_PATH, cwd);
1918 env_copy->u.cwd = cwd[0];
1921 LocalFree (env_copy);
1923 FreeEnvironmentStringsW (env);
1927 /*****************************************************************************
1930 * endlocal pops the environment off a stack
1931 * Note: When searching for '=', search from WCHAR position 1, to handle
1932 * special internal environment variables =C:, =D: etc
1934 void WCMD_endlocal (void) {
1935 WCHAR *env, *old, *p;
1936 struct env_stack *temp;
1939 if (!saved_environment)
1942 /* pop the old environment from the stack */
1943 temp = saved_environment;
1944 saved_environment = temp->next;
1946 /* delete the current environment, totally */
1947 env = GetEnvironmentStringsW ();
1948 old = WCMD_dupenv (GetEnvironmentStringsW ());
1951 n = strlenW(&old[len]) + 1;
1952 p = strchrW(&old[len] + 1, '=');
1956 SetEnvironmentVariableW (&old[len], NULL);
1961 FreeEnvironmentStringsW (env);
1963 /* restore old environment */
1964 env = temp->strings;
1967 n = strlenW(&env[len]) + 1;
1968 p = strchrW(&env[len] + 1, '=');
1972 SetEnvironmentVariableW (&env[len], p);
1977 /* Restore current drive letter */
1978 if (IsCharAlphaW(temp->u.cwd)) {
1980 WCHAR cwd[MAX_PATH];
1981 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1983 wsprintfW(envvar, fmt, temp->u.cwd);
1984 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1985 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1986 SetCurrentDirectoryW(cwd);
1994 /*****************************************************************************
1995 * WCMD_setshow_attrib
1997 * Display and optionally sets DOS attributes on a file or directory
2001 void WCMD_setshow_attrib (void) {
2005 WIN32_FIND_DATAW fd;
2006 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
2007 WCHAR *name = param1;
2009 DWORD attrib_clear=0;
2011 if (param1[0] == '+' || param1[0] == '-') {
2013 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
2014 switch (param1[1]) {
2015 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
2016 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
2017 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
2018 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
2020 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2023 switch (param1[0]) {
2024 case '+': attrib_set = attrib; break;
2025 case '-': attrib_clear = attrib; break;
2030 if (strlenW(name) == 0) {
2031 static const WCHAR slashStarW[] = {'\\','*','\0'};
2033 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
2034 strcatW (name, slashStarW);
2037 hff = FindFirstFileW(name, &fd);
2038 if (hff == INVALID_HANDLE_VALUE) {
2039 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
2043 if (attrib_set || attrib_clear) {
2044 fd.dwFileAttributes &= ~attrib_clear;
2045 fd.dwFileAttributes |= attrib_set;
2046 if (!fd.dwFileAttributes)
2047 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
2048 SetFileAttributesW(name, fd.dwFileAttributes);
2050 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
2051 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
2054 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
2057 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
2060 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
2063 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
2066 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
2069 WCMD_output (fmt, flags, fd.cFileName);
2070 for (count=0; count < 8; count++) flags[count] = ' ';
2072 } while (FindNextFileW(hff, &fd) != 0);
2077 /*****************************************************************************
2078 * WCMD_setshow_default
2080 * Set/Show the current default directory
2083 void WCMD_setshow_default (const WCHAR *command) {
2089 WIN32_FIND_DATAW fd;
2091 static const WCHAR parmD[] = {'/','D','\0'};
2093 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2095 /* Skip /D and trailing whitespace if on the front of the command line */
2096 if (CompareStringW(LOCALE_USER_DEFAULT,
2097 NORM_IGNORECASE | SORT_STRINGSORT,
2098 command, 2, parmD, -1) == CSTR_EQUAL) {
2100 while (*command && *command==' ') command++;
2103 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2104 if (strlenW(command) == 0) {
2105 strcatW (cwd, newline);
2109 /* Remove any double quotes, which may be in the
2110 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2113 if (*command != '"') *pos++ = *command;
2118 /* Search for appropriate directory */
2119 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2120 hff = FindFirstFileW(string, &fd);
2121 while (hff != INVALID_HANDLE_VALUE) {
2122 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2123 WCHAR fpath[MAX_PATH];
2125 WCHAR dir[MAX_PATH];
2126 WCHAR fname[MAX_PATH];
2127 WCHAR ext[MAX_PATH];
2128 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2130 /* Convert path into actual directory spec */
2131 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2132 WCMD_splitpath(fpath, drive, dir, fname, ext);
2135 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2138 hff = INVALID_HANDLE_VALUE;
2142 /* Step on to next match */
2143 if (FindNextFileW(hff, &fd) == 0) {
2145 hff = INVALID_HANDLE_VALUE;
2150 /* Change to that directory */
2151 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2153 status = SetCurrentDirectoryW(string);
2156 WCMD_print_error ();
2160 /* Save away the actual new directory, to store as current location */
2161 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2163 /* Restore old directory if drive letter would change, and
2164 CD x:\directory /D (or pushd c:\directory) not supplied */
2165 if ((strstrW(quals, parmD) == NULL) &&
2166 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2167 SetCurrentDirectoryW(cwd);
2171 /* Set special =C: type environment variable, for drive letter of
2172 change of directory, even if path was restored due to missing
2173 /D (allows changing drive letter when not resident on that
2175 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2177 strcpyW(env, equalW);
2178 memcpy(env+1, string, 2 * sizeof(WCHAR));
2180 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2181 SetEnvironmentVariableW(env, string);
2188 /****************************************************************************
2191 * Set/Show the system date
2192 * FIXME: Can't change date yet
2195 void WCMD_setshow_date (void) {
2197 WCHAR curdate[64], buffer[64];
2199 static const WCHAR parmT[] = {'/','T','\0'};
2201 if (strlenW(param1) == 0) {
2202 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2203 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2204 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2205 if (strstrW (quals, parmT) == NULL) {
2206 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2207 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2208 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2210 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2214 else WCMD_print_error ();
2217 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2221 /****************************************************************************
2224 static int WCMD_compare( const void *a, const void *b )
2227 const WCHAR * const *str_a = a, * const *str_b = b;
2228 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2229 *str_a, -1, *str_b, -1 );
2230 if( r == CSTR_LESS_THAN ) return -1;
2231 if( r == CSTR_GREATER_THAN ) return 1;
2235 /****************************************************************************
2236 * WCMD_setshow_sortenv
2238 * sort variables into order for display
2239 * Optionally only display those who start with a stub
2240 * returns the count displayed
2242 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2244 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2247 if (stub) stublen = strlenW(stub);
2249 /* count the number of strings, and the total length */
2251 len += (strlenW(&s[len]) + 1);
2255 /* add the strings to an array */
2256 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2260 for( i=1; i<count; i++ )
2261 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2263 /* sort the array */
2264 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2267 for( i=0; i<count; i++ ) {
2268 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2269 NORM_IGNORECASE | SORT_STRINGSORT,
2270 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2271 /* Don't display special internal variables */
2272 if (str[i][0] != '=') {
2273 WCMD_output_asis(str[i]);
2274 WCMD_output_asis(newline);
2281 return displayedcount;
2284 /****************************************************************************
2287 * Set/Show the environment variables
2290 void WCMD_setshow_env (WCHAR *s) {
2295 static const WCHAR parmP[] = {'/','P','\0'};
2297 if (param1[0] == 0x00 && quals[0] == 0x00) {
2298 env = GetEnvironmentStringsW();
2299 WCMD_setshow_sortenv( env, NULL );
2303 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2304 if (CompareStringW(LOCALE_USER_DEFAULT,
2305 NORM_IGNORECASE | SORT_STRINGSORT,
2306 s, 2, parmP, -1) == CSTR_EQUAL) {
2307 WCHAR string[MAXSTRING];
2311 while (*s && *s==' ') s++;
2313 WCMD_opt_s_strip_quotes(s);
2315 /* If no parameter, or no '=' sign, return an error */
2316 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2317 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2321 /* Output the prompt */
2323 if (strlenW(p) != 0) WCMD_output(p);
2325 /* Read the reply */
2326 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2327 sizeof(string)/sizeof(WCHAR), &count, NULL);
2329 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2330 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2331 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2332 wine_dbgstr_w(string));
2333 status = SetEnvironmentVariableW(s, string);
2340 WCMD_opt_s_strip_quotes(s);
2341 p = strchrW (s, '=');
2343 env = GetEnvironmentStringsW();
2344 if (WCMD_setshow_sortenv( env, s ) == 0) {
2345 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2352 if (strlenW(p) == 0) p = NULL;
2353 status = SetEnvironmentVariableW(s, p);
2354 gle = GetLastError();
2355 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2357 } else if ((!status)) WCMD_print_error();
2361 /****************************************************************************
2364 * Set/Show the path environment variable
2367 void WCMD_setshow_path (const WCHAR *command) {
2371 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2372 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2374 if (strlenW(param1) == 0) {
2375 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2377 WCMD_output_asis ( pathEqW);
2378 WCMD_output_asis ( string);
2379 WCMD_output_asis ( newline);
2382 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2386 if (*command == '=') command++; /* Skip leading '=' */
2387 status = SetEnvironmentVariableW(pathW, command);
2388 if (!status) WCMD_print_error();
2392 /****************************************************************************
2393 * WCMD_setshow_prompt
2395 * Set or show the command prompt.
2398 void WCMD_setshow_prompt (void) {
2401 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2403 if (strlenW(param1) == 0) {
2404 SetEnvironmentVariableW(promptW, NULL);
2408 while ((*s == '=') || (*s == ' ')) s++;
2409 if (strlenW(s) == 0) {
2410 SetEnvironmentVariableW(promptW, NULL);
2412 else SetEnvironmentVariableW(promptW, s);
2416 /****************************************************************************
2419 * Set/Show the system time
2420 * FIXME: Can't change time yet
2423 void WCMD_setshow_time (void) {
2425 WCHAR curtime[64], buffer[64];
2428 static const WCHAR parmT[] = {'/','T','\0'};
2430 if (strlenW(param1) == 0) {
2432 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2433 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2434 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2435 if (strstrW (quals, parmT) == NULL) {
2436 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2437 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2438 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2440 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2444 else WCMD_print_error ();
2447 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2451 /****************************************************************************
2454 * Shift batch parameters.
2455 * Optional /n says where to start shifting (n=0-8)
2458 void WCMD_shift (const WCHAR *command) {
2461 if (context != NULL) {
2462 WCHAR *pos = strchrW(command, '/');
2467 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2468 start = (*(pos+1) - '0');
2470 SetLastError(ERROR_INVALID_PARAMETER);
2475 WINE_TRACE("Shifting variables, starting at %d\n", start);
2476 for (i=start;i<=8;i++) {
2477 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2479 context -> shift_count[9] = context -> shift_count[9] + 1;
2484 /****************************************************************************
2487 * Set the console title
2489 void WCMD_title (const WCHAR *command) {
2490 SetConsoleTitleW(command);
2493 /****************************************************************************
2496 * Copy a file to standard output.
2499 void WCMD_type (WCHAR *command) {
2502 WCHAR *argN = command;
2503 BOOL writeHeaders = FALSE;
2505 if (param1[0] == 0x00) {
2506 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2510 if (param2[0] != 0x00) writeHeaders = TRUE;
2512 /* Loop through all args */
2515 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2523 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2524 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2525 FILE_ATTRIBUTE_NORMAL, NULL);
2526 if (h == INVALID_HANDLE_VALUE) {
2527 WCMD_print_error ();
2528 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2532 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2533 WCMD_output(fmt, thisArg);
2535 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2536 if (count == 0) break; /* ReadFile reports success on EOF! */
2538 WCMD_output_asis (buffer);
2545 /****************************************************************************
2548 * Output either a file or stdin to screen in pages
2551 void WCMD_more (WCHAR *command) {
2554 WCHAR *argN = command;
2556 WCHAR moreStrPage[100];
2559 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2560 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2561 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2562 ')',' ','-','-','\n','\0'};
2563 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2565 /* Prefix the NLS more with '-- ', then load the text */
2567 strcpyW(moreStr, moreStart);
2568 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2569 (sizeof(moreStr)/sizeof(WCHAR))-3);
2571 if (param1[0] == 0x00) {
2573 /* Wine implements pipes via temporary files, and hence stdin is
2574 effectively reading from the file. This means the prompts for
2575 more are satisfied by the next line from the input (file). To
2576 avoid this, ensure stdin is to the console */
2577 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2578 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2579 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2580 FILE_ATTRIBUTE_NORMAL, 0);
2581 WINE_TRACE("No parms - working probably in pipe mode\n");
2582 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2584 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2585 once you get in this bit unless due to a pipe, its going to end badly... */
2586 wsprintfW(moreStrPage, moreFmt, moreStr);
2588 WCMD_enter_paged_mode(moreStrPage);
2589 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2590 if (count == 0) break; /* ReadFile reports success on EOF! */
2592 WCMD_output_asis (buffer);
2594 WCMD_leave_paged_mode();
2596 /* Restore stdin to what it was */
2597 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2598 CloseHandle(hConIn);
2602 BOOL needsPause = FALSE;
2604 /* Loop through all args */
2605 WINE_TRACE("Parms supplied - working through each file\n");
2606 WCMD_enter_paged_mode(moreStrPage);
2609 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2617 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2618 WCMD_leave_paged_mode();
2619 WCMD_output_asis(moreStrPage);
2620 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2621 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2622 WCMD_enter_paged_mode(moreStrPage);
2626 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2627 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2628 FILE_ATTRIBUTE_NORMAL, NULL);
2629 if (h == INVALID_HANDLE_VALUE) {
2630 WCMD_print_error ();
2631 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2635 ULONG64 fileLen = 0;
2636 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2638 /* Get the file size */
2639 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2640 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2643 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2644 if (count == 0) break; /* ReadFile reports success on EOF! */
2648 /* Update % count (would be used in WCMD_output_asis as prompt) */
2649 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2651 WCMD_output_asis (buffer);
2657 WCMD_leave_paged_mode();
2661 /****************************************************************************
2664 * Display verify flag.
2665 * FIXME: We don't actually do anything with the verify flag other than toggle
2669 void WCMD_verify (const WCHAR *command) {
2673 count = strlenW(command);
2675 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2676 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2679 if (lstrcmpiW(command, onW) == 0) {
2683 else if (lstrcmpiW(command, offW) == 0) {
2687 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2690 /****************************************************************************
2693 * Display version info.
2696 void WCMD_version (void) {
2698 WCMD_output (version_string);
2702 /****************************************************************************
2705 * Display volume info and/or set volume label. Returns 0 if error.
2708 int WCMD_volume (int mode, const WCHAR *path) {
2710 DWORD count, serial;
2711 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2714 if (strlenW(path) == 0) {
2715 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2717 WCMD_print_error ();
2720 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2721 &serial, NULL, NULL, NULL, 0);
2724 static const WCHAR fmt[] = {'%','s','\\','\0'};
2725 if ((path[1] != ':') || (strlenW(path) != 2)) {
2726 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2729 wsprintfW (curdir, fmt, path);
2730 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2735 WCMD_print_error ();
2738 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2739 curdir[0], label, HIWORD(serial), LOWORD(serial));
2741 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2742 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2743 sizeof(string)/sizeof(WCHAR), &count, NULL);
2745 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2746 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2748 if (strlenW(path) != 0) {
2749 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2752 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2758 /**************************************************************************
2761 * Exit either the process, or just this batch program
2765 void WCMD_exit (CMD_LIST **cmdList) {
2767 static const WCHAR parmB[] = {'/','B','\0'};
2768 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2770 if (context && lstrcmpiW(quals, parmB) == 0) {
2772 context -> skip_rest = TRUE;
2780 /*****************************************************************************
2783 * Lists or sets file associations (assoc = TRUE)
2784 * Lists or sets file types (assoc = FALSE)
2786 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2789 DWORD accessOptions = KEY_READ;
2791 LONG rc = ERROR_SUCCESS;
2792 WCHAR keyValue[MAXSTRING];
2793 DWORD valueLen = MAXSTRING;
2795 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2796 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2798 /* See if parameter includes '=' */
2800 newValue = strchrW(command, '=');
2801 if (newValue) accessOptions |= KEY_WRITE;
2803 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2804 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2805 accessOptions, &key) != ERROR_SUCCESS) {
2806 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2810 /* If no parameters then list all associations */
2811 if (*command == 0x00) {
2814 /* Enumerate all the keys */
2815 while (rc != ERROR_NO_MORE_ITEMS) {
2816 WCHAR keyName[MAXSTRING];
2819 /* Find the next value */
2820 nameLen = MAXSTRING;
2821 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2823 if (rc == ERROR_SUCCESS) {
2825 /* Only interested in extension ones if assoc, or others
2827 if ((keyName[0] == '.' && assoc) ||
2828 (!(keyName[0] == '.') && (!assoc)))
2830 WCHAR subkey[MAXSTRING];
2831 strcpyW(subkey, keyName);
2832 if (!assoc) strcatW(subkey, shOpCmdW);
2834 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2836 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2837 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2838 WCMD_output_asis(keyName);
2839 WCMD_output_asis(equalW);
2840 /* If no default value found, leave line empty after '=' */
2841 if (rc == ERROR_SUCCESS) {
2842 WCMD_output_asis(keyValue);
2844 WCMD_output_asis(newline);
2845 RegCloseKey(readKey);
2853 /* Parameter supplied - if no '=' on command line, its a query */
2854 if (newValue == NULL) {
2856 WCHAR subkey[MAXSTRING];
2858 /* Query terminates the parameter at the first space */
2859 strcpyW(keyValue, command);
2860 space = strchrW(keyValue, ' ');
2861 if (space) *space=0x00;
2863 /* Set up key name */
2864 strcpyW(subkey, keyValue);
2865 if (!assoc) strcatW(subkey, shOpCmdW);
2867 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2869 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2870 WCMD_output_asis(command);
2871 WCMD_output_asis(equalW);
2872 /* If no default value found, leave line empty after '=' */
2873 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2874 WCMD_output_asis(newline);
2875 RegCloseKey(readKey);
2878 WCHAR msgbuffer[MAXSTRING];
2879 WCHAR outbuffer[MAXSTRING];
2881 /* Load the translated 'File association not found' */
2883 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2885 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2887 wsprintfW(outbuffer, msgbuffer, keyValue);
2888 WCMD_output_asis(outbuffer);
2892 /* Not a query - its a set or clear of a value */
2895 WCHAR subkey[MAXSTRING];
2897 /* Get pointer to new value */
2901 /* Set up key name */
2902 strcpyW(subkey, command);
2903 if (!assoc) strcatW(subkey, shOpCmdW);
2905 /* If nothing after '=' then clear value - only valid for ASSOC */
2906 if (*newValue == 0x00) {
2908 if (assoc) rc = RegDeleteKeyW(key, command);
2909 if (assoc && rc == ERROR_SUCCESS) {
2910 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2912 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2917 WCHAR msgbuffer[MAXSTRING];
2918 WCHAR outbuffer[MAXSTRING];
2920 /* Load the translated 'File association not found' */
2922 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2923 sizeof(msgbuffer)/sizeof(WCHAR));
2925 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2926 sizeof(msgbuffer)/sizeof(WCHAR));
2928 wsprintfW(outbuffer, msgbuffer, keyValue);
2929 WCMD_output_asis(outbuffer);
2933 /* It really is a set value = contents */
2935 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2936 accessOptions, NULL, &readKey, NULL);
2937 if (rc == ERROR_SUCCESS) {
2938 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2939 (LPBYTE)newValue, strlenW(newValue));
2940 RegCloseKey(readKey);
2943 if (rc != ERROR_SUCCESS) {
2947 WCMD_output_asis(command);
2948 WCMD_output_asis(equalW);
2949 WCMD_output_asis(newValue);
2950 WCMD_output_asis(newline);
2960 /****************************************************************************
2963 * Clear the terminal screen.
2966 void WCMD_color (void) {
2968 /* Emulate by filling the screen from the top left to bottom right with
2969 spaces, then moving the cursor to the top left afterwards */
2970 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2971 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2973 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2974 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2978 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2984 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2989 /* Convert the color hex digits */
2990 if (param1[0] == 0x00) {
2991 color = defaultColor;
2993 color = strtoulW(param1, NULL, 16);
2996 /* Fail if fg == bg color */
2997 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3002 /* Set the current screen contents and ensure all future writes
3003 remain this color */
3004 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3005 SetConsoleTextAttribute(hStdOut, color);