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, WCHAR *firstcmd, WCHAR *variable,
45 WCHAR *value, BOOL isIF, BOOL conditionTRUE);
47 struct env_stack *saved_environment;
48 struct env_stack *pushd_directories;
50 extern HINSTANCE hinst;
51 extern WCHAR inbuilt[][10];
52 extern int echo_mode, verify_mode, defaultColor;
53 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
54 extern BATCH_CONTEXT *context;
55 extern DWORD errorlevel;
57 static const WCHAR dotW[] = {'.','\0'};
58 static const WCHAR dotdotW[] = {'.','.','\0'};
59 static const WCHAR slashW[] = {'\\','\0'};
60 static const WCHAR starW[] = {'*','\0'};
61 static const WCHAR equalW[] = {'=','\0'};
62 static const WCHAR fslashW[] = {'/','\0'};
63 static const WCHAR onW[] = {'O','N','\0'};
64 static const WCHAR offW[] = {'O','F','F','\0'};
65 static const WCHAR parmY[] = {'/','Y','\0'};
66 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
67 static const WCHAR nullW[] = {'\0'};
69 /**************************************************************************
72 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
75 * Returns True if Y (or A) answer is selected
76 * If optionAll contains a pointer, ALL is allowed, and if answered
80 static BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
82 WCHAR msgbuffer[MAXSTRING];
83 WCHAR Ybuffer[MAXSTRING];
84 WCHAR Nbuffer[MAXSTRING];
85 WCHAR Abuffer[MAXSTRING];
86 WCHAR answer[MAX_PATH] = {'\0'};
89 /* Load the translated 'Are you sure', plus valid answers */
90 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
91 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
92 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
93 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
95 /* Loop waiting on a Y or N */
96 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
97 static const WCHAR startBkt[] = {' ','(','\0'};
98 static const WCHAR endBkt[] = {')','?','\0'};
100 WCMD_output_asis (message);
102 WCMD_output_asis (msgbuffer);
104 WCMD_output_asis (startBkt);
105 WCMD_output_asis (Ybuffer);
106 WCMD_output_asis (fslashW);
107 WCMD_output_asis (Nbuffer);
109 WCMD_output_asis (fslashW);
110 WCMD_output_asis (Abuffer);
112 WCMD_output_asis (endBkt);
113 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
114 sizeof(answer)/sizeof(WCHAR), &count, NULL);
115 answer[0] = toupperW(answer[0]);
118 /* Return the answer */
119 return ((answer[0] == Ybuffer[0]) ||
120 (optionAll && (answer[0] == Abuffer[0])));
123 /****************************************************************************
126 * Clear the terminal screen.
129 void WCMD_clear_screen (void) {
131 /* Emulate by filling the screen from the top left to bottom right with
132 spaces, then moving the cursor to the top left afterwards */
133 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
134 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
136 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
141 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
145 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
146 SetConsoleCursorPosition(hStdOut, topLeft);
150 /****************************************************************************
153 * Change the default i/o device (ie redirect STDin/STDout).
156 void WCMD_change_tty (void) {
158 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
162 /****************************************************************************
167 void WCMD_choice (WCHAR * command) {
169 static const WCHAR bellW[] = {7,0};
170 static const WCHAR commaW[] = {',',0};
171 static const WCHAR bracket_open[] = {'[',0};
172 static const WCHAR bracket_close[] = {']','?',0};
177 WCHAR *my_command = NULL;
178 WCHAR opt_default = 0;
179 DWORD opt_timeout = 0;
186 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
189 my_command = WCMD_strdupW(WCMD_strtrim_leading_spaces(command));
193 ptr = WCMD_strtrim_leading_spaces(my_command);
194 while (*ptr == '/') {
195 switch (toupperW(ptr[1])) {
198 /* the colon is optional */
202 if (!*ptr || isspaceW(*ptr)) {
203 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
204 HeapFree(GetProcessHeap(), 0, my_command);
208 /* remember the allowed keys (overwrite previous /C option) */
210 while (*ptr && (!isspaceW(*ptr)))
214 /* terminate allowed chars */
216 ptr = WCMD_strtrim_leading_spaces(&ptr[1]);
218 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
223 ptr = WCMD_strtrim_leading_spaces(&ptr[2]);
228 ptr = WCMD_strtrim_leading_spaces(&ptr[2]);
233 /* the colon is optional */
237 opt_default = *ptr++;
239 if (!opt_default || (*ptr != ',')) {
240 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
241 HeapFree(GetProcessHeap(), 0, my_command);
247 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
253 opt_timeout = atoiW(answer);
255 ptr = WCMD_strtrim_leading_spaces(ptr);
259 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
260 HeapFree(GetProcessHeap(), 0, my_command);
266 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
269 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
271 /* use default keys, when needed: localized versions of "Y"es and "No" */
273 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
274 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
279 /* print the question, when needed */
281 WCMD_output_asis(ptr);
285 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
289 /* print a list of all allowed answers inside brackets */
290 WCMD_output_asis(bracket_open);
293 while ((answer[0] = *ptr++)) {
294 WCMD_output_asis(answer);
296 WCMD_output_asis(commaW);
298 WCMD_output_asis(bracket_close);
303 /* FIXME: Add support for option /T */
304 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count, NULL);
307 answer[0] = toupperW(answer[0]);
309 ptr = strchrW(opt_c, answer[0]);
311 WCMD_output_asis(answer);
312 WCMD_output(newline);
314 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
316 errorlevel = (ptr - opt_c) + 1;
317 WINE_TRACE("answer: %d\n", errorlevel);
318 HeapFree(GetProcessHeap(), 0, my_command);
323 /* key not allowed: play the bell */
324 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
325 WCMD_output_asis(bellW);
330 /****************************************************************************
333 * Copy a file or wildcarded set.
334 * FIXME: Add support for a+b+c type syntax
337 void WCMD_copy (void) {
342 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
344 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
345 BOOL copyToDir = FALSE;
346 WCHAR srcspec[MAX_PATH];
350 WCHAR fname[MAX_PATH];
353 if (param1[0] == 0x00) {
354 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
358 /* Convert source into full spec */
359 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
360 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
361 if (srcpath[strlenW(srcpath) - 1] == '\\')
362 srcpath[strlenW(srcpath) - 1] = '\0';
364 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
365 attribs = GetFileAttributesW(srcpath);
369 strcpyW(srcspec, srcpath);
371 /* If a directory, then add \* on the end when searching */
372 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
373 strcatW(srcpath, slashW);
374 strcatW(srcspec, slashW);
375 strcatW(srcspec, starW);
377 WCMD_splitpath(srcpath, drive, dir, fname, ext);
378 strcpyW(srcpath, drive);
379 strcatW(srcpath, dir);
382 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
384 /* If no destination supplied, assume current directory */
385 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
386 if (param2[0] == 0x00) {
387 strcpyW(param2, dotW);
390 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
391 if (outpath[strlenW(outpath) - 1] == '\\')
392 outpath[strlenW(outpath) - 1] = '\0';
393 attribs = GetFileAttributesW(outpath);
394 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
395 strcatW (outpath, slashW);
398 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
399 wine_dbgstr_w(outpath), copyToDir);
401 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
402 if (strstrW (quals, parmNoY))
404 else if (strstrW (quals, parmY))
407 /* By default, we will force the overwrite in batch mode and ask for
408 * confirmation in interactive mode. */
411 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
412 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
413 * default behavior. */
414 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
415 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
416 if (!lstrcmpiW (copycmd, parmY))
418 else if (!lstrcmpiW (copycmd, parmNoY))
423 /* Loop through all source files */
424 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
425 hff = FindFirstFileW(srcspec, &fd);
426 if (hff != INVALID_HANDLE_VALUE) {
428 WCHAR outname[MAX_PATH];
429 WCHAR srcname[MAX_PATH];
430 BOOL overwrite = force;
432 /* Destination is either supplied filename, or source name in
433 supplied destination directory */
434 strcpyW(outname, outpath);
435 if (copyToDir) strcatW(outname, fd.cFileName);
436 strcpyW(srcname, srcpath);
437 strcatW(srcname, fd.cFileName);
439 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
440 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
442 /* Skip . and .., and directories */
443 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
445 WINE_TRACE("Skipping directories\n");
448 /* Prompt before overwriting */
449 else if (!overwrite) {
450 attribs = GetFileAttributesW(outname);
451 if (attribs != INVALID_FILE_ATTRIBUTES) {
452 WCHAR buffer[MAXSTRING];
453 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
454 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
456 else overwrite = TRUE;
459 /* Do the copy as appropriate */
461 status = CopyFileW(srcname, outname, FALSE);
462 if (!status) WCMD_print_error ();
465 } while (FindNextFileW(hff, &fd) != 0);
468 status = ERROR_FILE_NOT_FOUND;
473 /****************************************************************************
476 * Create a directory.
478 * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
479 * they do not already exist.
482 static BOOL create_full_path(WCHAR* path)
488 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path)+1) * sizeof(WCHAR));
489 strcpyW(new_path,path);
491 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
492 new_path[len - 1] = 0;
494 while (!CreateDirectoryW(new_path,NULL))
497 DWORD last_error = GetLastError();
498 if (last_error == ERROR_ALREADY_EXISTS)
501 if (last_error != ERROR_PATH_NOT_FOUND)
507 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
513 len = slash - new_path;
515 if (!create_full_path(new_path))
520 new_path[len] = '\\';
522 HeapFree(GetProcessHeap(),0,new_path);
526 void WCMD_create_dir (void) {
528 if (param1[0] == 0x00) {
529 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
532 if (!create_full_path(param1)) WCMD_print_error ();
535 /* Parse the /A options given by the user on the commandline
536 * into a bitmask of wanted attributes (*wantSet),
537 * and a bitmask of unwanted attributes (*wantClear).
539 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
540 static const WCHAR parmA[] = {'/','A','\0'};
543 /* both are strictly 'out' parameters */
547 /* For each /A argument */
548 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
552 /* Skip optional : */
555 /* For each of the attribute specifier chars to this /A option */
556 for (; *p != 0 && *p != '/'; p++) {
565 /* Convert the attribute specifier to a bit in one of the masks */
567 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
568 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
569 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
570 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
572 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
582 /****************************************************************************
585 * Delete a file or wildcarded set.
588 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
589 * - Each set is a pattern, eg /ahr /as-r means
590 * readonly+hidden OR nonreadonly system files
591 * - The '-' applies to a single field, ie /a:-hr means read only
595 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
598 int argsProcessed = 0;
599 WCHAR *argN = command;
600 BOOL foundAny = FALSE;
601 static const WCHAR parmQ[] = {'/','Q','\0'};
602 static const WCHAR parmP[] = {'/','P','\0'};
603 static const WCHAR parmS[] = {'/','S','\0'};
604 static const WCHAR parmF[] = {'/','F','\0'};
606 DWORD unwanted_attrs;
608 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
610 /* If not recursing, clear error flag */
611 if (expectDir) errorlevel = 0;
613 /* Loop through all args */
615 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
616 WCHAR argCopy[MAX_PATH];
618 if (argN && argN[0] != '/') {
622 WCHAR fpath[MAX_PATH];
624 BOOL handleParm = TRUE;
626 static const WCHAR anyExt[]= {'.','*','\0'};
628 strcpyW(argCopy, thisArg);
629 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
630 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
633 /* If filename part of parameter is * or *.*, prompt unless
635 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
639 WCHAR fname[MAX_PATH];
642 /* Convert path into actual directory spec */
643 GetFullPathNameW(argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
644 WCMD_splitpath(fpath, drive, dir, fname, ext);
646 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
647 if ((strcmpW(fname, starW) == 0) &&
648 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
650 WCHAR question[MAXSTRING];
651 static const WCHAR fmt[] = {'%','s',' ','\0'};
653 /* Note: Flag as found, to avoid file not found message */
656 /* Ask for confirmation */
657 wsprintfW(question, fmt, fpath);
658 ok = WCMD_ask_confirm(question, TRUE, NULL);
660 /* Abort if answer is 'N' */
665 /* First, try to delete in the current directory */
666 hff = FindFirstFileW(argCopy, &fd);
667 if (hff == INVALID_HANDLE_VALUE) {
673 /* Support del <dirname> by just deleting all files dirname\* */
674 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
675 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
676 WCHAR modifiedParm[MAX_PATH];
677 static const WCHAR slashStar[] = {'\\','*','\0'};
679 strcpyW(modifiedParm, argCopy);
680 strcatW(modifiedParm, slashStar);
683 WCMD_delete(modifiedParm, FALSE);
685 } else if (handleParm) {
687 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
688 strcpyW (fpath, argCopy);
690 p = strrchrW (fpath, '\\');
693 strcatW (fpath, fd.cFileName);
695 else strcpyW (fpath, fd.cFileName);
696 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
699 /* Handle attribute matching (/A) */
700 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
701 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
703 /* /P means prompt for each file */
704 if (ok && strstrW (quals, parmP) != NULL) {
705 WCHAR question[MAXSTRING];
707 /* Ask for confirmation */
708 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
709 ok = WCMD_ask_confirm(question, FALSE, NULL);
712 /* Only proceed if ok to */
715 /* If file is read only, and /F supplied, delete it */
716 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
717 strstrW (quals, parmF) != NULL) {
718 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
721 /* Now do the delete */
722 if (!DeleteFileW(fpath)) WCMD_print_error ();
726 } while (FindNextFileW(hff, &fd) != 0);
730 /* Now recurse into all subdirectories handling the parameter in the same way */
731 if (strstrW (quals, parmS) != NULL) {
733 WCHAR thisDir[MAX_PATH];
738 WCHAR fname[MAX_PATH];
741 /* Convert path into actual directory spec */
742 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
743 WCMD_splitpath(thisDir, drive, dir, fname, ext);
745 strcpyW(thisDir, drive);
746 strcatW(thisDir, dir);
747 cPos = strlenW(thisDir);
749 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
751 /* Append '*' to the directory */
753 thisDir[cPos+1] = 0x00;
755 hff = FindFirstFileW(thisDir, &fd);
757 /* Remove residual '*' */
758 thisDir[cPos] = 0x00;
760 if (hff != INVALID_HANDLE_VALUE) {
761 DIRECTORY_STACK *allDirs = NULL;
762 DIRECTORY_STACK *lastEntry = NULL;
765 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
766 (strcmpW(fd.cFileName, dotdotW) != 0) &&
767 (strcmpW(fd.cFileName, dotW) != 0)) {
769 DIRECTORY_STACK *nextDir;
770 WCHAR subParm[MAX_PATH];
772 /* Work out search parameter in sub dir */
773 strcpyW (subParm, thisDir);
774 strcatW (subParm, fd.cFileName);
775 strcatW (subParm, slashW);
776 strcatW (subParm, fname);
777 strcatW (subParm, ext);
778 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
780 /* Allocate memory, add to list */
781 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
782 if (allDirs == NULL) allDirs = nextDir;
783 if (lastEntry != NULL) lastEntry->next = nextDir;
785 nextDir->next = NULL;
786 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
787 (strlenW(subParm)+1) * sizeof(WCHAR));
788 strcpyW(nextDir->dirName, subParm);
790 } while (FindNextFileW(hff, &fd) != 0);
793 /* Go through each subdir doing the delete */
794 while (allDirs != NULL) {
795 DIRECTORY_STACK *tempDir;
797 tempDir = allDirs->next;
798 found |= WCMD_delete (allDirs->dirName, FALSE);
800 HeapFree(GetProcessHeap(),0,allDirs->dirName);
801 HeapFree(GetProcessHeap(),0,allDirs);
806 /* Keep running total to see if any found, and if not recursing
807 issue error message */
811 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
818 /* Handle no valid args */
819 if (argsProcessed == 0) {
820 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
826 /****************************************************************************
829 * Echo input to the screen (or not). We don't try to emulate the bugs
830 * in DOS (try typing "ECHO ON AGAIN" for an example).
833 void WCMD_echo (const WCHAR *command) {
836 const WCHAR *origcommand = command;
838 if (command[0]==' ' || command[0]=='.')
840 count = strlenW(command);
841 if (count == 0 && origcommand[0]!='.') {
842 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
843 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
846 if (lstrcmpiW(command, onW) == 0) {
850 if (lstrcmpiW(command, offW) == 0) {
854 WCMD_output_asis (command);
855 WCMD_output (newline);
859 /**************************************************************************
862 * Batch file loop processing.
864 * On entry: cmdList contains the syntax up to the set
865 * next cmdList and all in that bracket contain the set data
866 * next cmdlist contains the DO cmd
867 * following that is either brackets or && entries (as per if)
871 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
876 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
877 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
878 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
884 BOOL expandDirs = FALSE;
885 BOOL useNumbers = FALSE;
886 BOOL doFileset = FALSE;
887 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
889 CMD_LIST *thisCmdStart;
892 /* Handle optional qualifiers (multiple are allowed) */
893 while (*curPos && *curPos == '/') {
894 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
896 switch (toupperW(*curPos)) {
897 case 'D': curPos++; expandDirs = TRUE; break;
898 case 'L': curPos++; useNumbers = TRUE; break;
900 /* Recursive is special case - /R can have an optional path following it */
901 /* filenamesets are another special case - /F can have an optional options following it */
905 BOOL isRecursive = (*curPos == 'R');
910 /* Skip whitespace */
912 while (*curPos && *curPos==' ') curPos++;
914 /* Next parm is either qualifier, path/options or variable -
915 only care about it if it is the path/options */
916 if (*curPos && *curPos != '/' && *curPos != '%') {
917 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
918 else WINE_FIXME("/F needs to handle options\n");
923 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
927 /* Skip whitespace between qualifiers */
928 while (*curPos && *curPos==' ') curPos++;
931 /* Skip whitespace before variable */
932 while (*curPos && *curPos==' ') curPos++;
934 /* Ensure line continues with variable */
935 if (!*curPos || *curPos != '%') {
936 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
940 /* Variable should follow */
942 while (curPos[i] && curPos[i]!=' ') i++;
943 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
945 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
948 /* Skip whitespace before IN */
949 while (*curPos && *curPos==' ') curPos++;
951 /* Ensure line continues with IN */
952 if (!*curPos || lstrcmpiW (curPos, inW)) {
953 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
957 /* Save away where the set of data starts and the variable */
958 thisDepth = (*cmdList)->bracketDepth;
959 *cmdList = (*cmdList)->nextcommand;
960 setStart = (*cmdList);
962 /* Skip until the close bracket */
963 WINE_TRACE("Searching %p as the set\n", *cmdList);
965 (*cmdList)->command != NULL &&
966 (*cmdList)->bracketDepth > thisDepth) {
967 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
968 *cmdList = (*cmdList)->nextcommand;
971 /* Skip the close bracket, if there is one */
972 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
974 /* Syntax error if missing close bracket, or nothing following it
975 and once we have the complete set, we expect a DO */
976 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
977 if ((*cmdList == NULL) ||
978 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
979 (*cmdList)->command, 3, doW, -1) != 2)) {
980 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
984 /* Save away the starting position for the commands (and offset for the
988 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
992 /* Loop through all set entries */
994 thisSet->command != NULL &&
995 thisSet->bracketDepth >= thisDepth) {
997 /* Loop through all entries on the same line */
1001 WINE_TRACE("Processing for set %p\n", thisSet);
1003 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
1006 * If the parameter within the set has a wildcard then search for matching files
1007 * otherwise do a literal substitution.
1009 static const WCHAR wildcards[] = {'*','?','\0'};
1010 thisCmdStart = cmdStart;
1013 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1015 if (!useNumbers && !doFileset) {
1016 if (strpbrkW (item, wildcards)) {
1017 hff = FindFirstFileW(item, &fd);
1018 if (hff != INVALID_HANDLE_VALUE) {
1020 BOOL isDirectory = FALSE;
1022 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1024 /* Handle as files or dirs appropriately, but ignore . and .. */
1025 if (isDirectory == expandDirs &&
1026 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1027 (strcmpW(fd.cFileName, dotW) != 0))
1029 thisCmdStart = cmdStart;
1030 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1031 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1032 fd.cFileName, FALSE, TRUE);
1035 } while (FindNextFileW(hff, &fd) != 0);
1039 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1042 } else if (useNumbers) {
1043 /* Convert the first 3 numbers to signed longs and save */
1044 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1045 /* else ignore them! */
1047 /* Filesets - either a list of files, or a command to run and parse the output */
1048 } else if (doFileset && *itemStart != '"') {
1051 WCHAR temp_file[MAX_PATH];
1053 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1054 wine_dbgstr_w(item));
1056 /* If backquote or single quote, we need to launch that command
1057 and parse the results - use a temporary file */
1058 if (*itemStart == '`' || *itemStart == '\'') {
1060 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1061 static const WCHAR redirOut[] = {'>','%','s','\0'};
1062 static const WCHAR cmdW[] = {'C','M','D','\0'};
1064 /* Remove trailing character */
1065 itemStart[strlenW(itemStart)-1] = 0x00;
1067 /* Get temp filename */
1068 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1069 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1071 /* Execute program and redirect output */
1072 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1073 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1075 /* Open the file, read line by line and process */
1076 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1077 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1080 /* Open the file, read line by line and process */
1081 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1082 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1085 /* Process the input file */
1086 if (input == INVALID_HANDLE_VALUE) {
1087 WCMD_print_error ();
1088 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1090 return; /* FOR loop aborts at first failure here */
1094 WCHAR buffer[MAXSTRING] = {'\0'};
1095 WCHAR *where, *parm;
1097 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1099 /* Skip blank lines*/
1100 parm = WCMD_parameter (buffer, 0, &where);
1101 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1102 wine_dbgstr_w(buffer));
1105 /* FIXME: The following should be moved into its own routine and
1106 reused for the string literal parsing below */
1107 thisCmdStart = cmdStart;
1108 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1109 cmdEnd = thisCmdStart;
1115 CloseHandle (input);
1118 /* Delete the temporary file */
1119 if (*itemStart == '`' || *itemStart == '\'') {
1120 DeleteFileW(temp_file);
1123 /* Filesets - A string literal */
1124 } else if (doFileset && *itemStart == '"') {
1125 WCHAR buffer[MAXSTRING] = {'\0'};
1126 WCHAR *where, *parm;
1128 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1129 strcpyW(buffer, item);
1130 parm = WCMD_parameter (buffer, 0, &where);
1131 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1132 wine_dbgstr_w(buffer));
1135 /* FIXME: The following should be moved into its own routine and
1136 reused for the string literal parsing below */
1137 thisCmdStart = cmdStart;
1138 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1139 cmdEnd = thisCmdStart;
1143 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1144 cmdEnd = thisCmdStart;
1148 /* Move onto the next set line */
1149 thisSet = thisSet->nextcommand;
1152 /* If /L is provided, now run the for loop */
1155 static const WCHAR fmt[] = {'%','d','\0'};
1157 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1158 numbers[0], numbers[2], numbers[1]);
1160 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1163 sprintfW(thisNum, fmt, i);
1164 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1166 thisCmdStart = cmdStart;
1167 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1168 cmdEnd = thisCmdStart;
1172 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1173 all processing, OR it should be pointing to the end of && processing OR
1174 it should be pointing at the NULL end of bracket for the DO. The return
1175 value needs to be the NEXT command to execute, which it either is, or
1176 we need to step over the closing bracket */
1178 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1182 /*****************************************************************************
1185 * Execute a command, and any && or bracketed follow on to the command. The
1186 * first command to be executed may not be at the front of the
1187 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1189 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1190 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1192 CMD_LIST *curPosition = *cmdList;
1193 int myDepth = (*cmdList)->bracketDepth;
1195 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1196 cmdList, wine_dbgstr_w(firstcmd),
1197 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1200 /* Skip leading whitespace between condition and the command */
1201 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1203 /* Process the first command, if there is one */
1204 if (conditionTRUE && firstcmd && *firstcmd) {
1205 WCHAR *command = WCMD_strdupW(firstcmd);
1206 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1207 HeapFree(GetProcessHeap(), 0, command);
1211 /* If it didn't move the position, step to next command */
1212 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1214 /* Process any other parts of the command */
1216 BOOL processThese = TRUE;
1218 if (isIF) processThese = conditionTRUE;
1221 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1223 /* execute all appropriate commands */
1224 curPosition = *cmdList;
1226 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1228 (*cmdList)->prevDelim,
1229 (*cmdList)->bracketDepth, myDepth);
1231 /* Execute any statements appended to the line */
1232 /* FIXME: Only if previous call worked for && or failed for || */
1233 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1234 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1235 if (processThese && (*cmdList)->command) {
1236 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1239 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1241 /* Execute any appended to the statement with (...) */
1242 } else if ((*cmdList)->bracketDepth > myDepth) {
1244 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1245 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1247 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1249 /* End of the command - does 'ELSE ' follow as the next command? */
1251 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1252 NORM_IGNORECASE | SORT_STRINGSORT,
1253 (*cmdList)->command, 5, ifElse, -1) == 2) {
1255 /* Swap between if and else processing */
1256 processThese = !processThese;
1258 /* Process the ELSE part */
1260 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1262 /* Skip leading whitespace between condition and the command */
1263 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1265 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1268 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1270 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1279 /**************************************************************************
1282 * Simple on-line help. Help text is stored in the resource file.
1285 void WCMD_give_help (WCHAR *command) {
1289 command = WCMD_strtrim_leading_spaces(command);
1290 if (strlenW(command) == 0) {
1291 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1294 for (i=0; i<=WCMD_EXIT; i++) {
1295 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1296 command, -1, inbuilt[i], -1) == 2) {
1297 WCMD_output_asis (WCMD_LoadMessage(i));
1301 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1306 /****************************************************************************
1309 * Batch file jump instruction. Not the most efficient algorithm ;-)
1310 * Prints error message if the specified label cannot be found - the file pointer is
1311 * then at EOF, effectively stopping the batch file.
1312 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1315 void WCMD_goto (CMD_LIST **cmdList) {
1317 WCHAR string[MAX_PATH];
1318 WCHAR current[MAX_PATH];
1320 /* Do not process any more parts of a processed multipart or multilines command */
1321 if (cmdList) *cmdList = NULL;
1323 if (param1[0] == 0x00) {
1324 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1327 if (context != NULL) {
1328 WCHAR *paramStart = param1, *str;
1329 static const WCHAR eofW[] = {':','e','o','f','\0'};
1331 /* Handle special :EOF label */
1332 if (lstrcmpiW (eofW, param1) == 0) {
1333 context -> skip_rest = TRUE;
1337 /* Support goto :label as well as goto label */
1338 if (*paramStart == ':') paramStart++;
1340 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1341 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1343 while (isspaceW (*str)) str++;
1347 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1350 /* ignore space at the end */
1352 if (lstrcmpiW (current, paramStart) == 0) return;
1355 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1360 /*****************************************************************************
1363 * Push a directory onto the stack
1366 void WCMD_pushd (WCHAR *command) {
1367 struct env_stack *curdir;
1369 static const WCHAR parmD[] = {'/','D','\0'};
1371 if (strchrW(command, '/') != NULL) {
1372 SetLastError(ERROR_INVALID_PARAMETER);
1377 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1378 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1379 if( !curdir || !thisdir ) {
1382 WINE_ERR ("out of memory\n");
1386 /* Change directory using CD code with /D parameter */
1387 strcpyW(quals, parmD);
1388 GetCurrentDirectoryW (1024, thisdir);
1390 WCMD_setshow_default(command);
1396 curdir -> next = pushd_directories;
1397 curdir -> strings = thisdir;
1398 if (pushd_directories == NULL) {
1399 curdir -> u.stackdepth = 1;
1401 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1403 pushd_directories = curdir;
1408 /*****************************************************************************
1411 * Pop a directory from the stack
1414 void WCMD_popd (void) {
1415 struct env_stack *temp = pushd_directories;
1417 if (!pushd_directories)
1420 /* pop the old environment from the stack, and make it the current dir */
1421 pushd_directories = temp->next;
1422 SetCurrentDirectoryW(temp->strings);
1423 LocalFree (temp->strings);
1427 /****************************************************************************
1430 * Batch file conditional.
1432 * On entry, cmdlist will point to command containing the IF, and optionally
1433 * the first command to execute (if brackets not found)
1434 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1435 * If ('s were found, execute all within that bracket
1436 * Command may optionally be followed by an ELSE - need to skip instructions
1437 * in the else using the same logic
1439 * FIXME: Much more syntax checking needed!
1442 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1444 int negate = 0, test = 0;
1445 WCHAR condition[MAX_PATH], *command, *s;
1446 static const WCHAR notW[] = {'n','o','t','\0'};
1447 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1448 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1449 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1450 static const WCHAR eqeqW[] = {'=','=','\0'};
1451 static const WCHAR parmI[] = {'/','I','\0'};
1453 if (!lstrcmpiW (param1, notW)) {
1455 strcpyW (condition, param2);
1458 strcpyW (condition, param1);
1460 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1462 if (!lstrcmpiW (condition, errlvlW)) {
1463 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1464 WCMD_parameter (p, 2+negate, &command);
1466 else if (!lstrcmpiW (condition, existW)) {
1467 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1470 WCMD_parameter (p, 2+negate, &command);
1472 else if (!lstrcmpiW (condition, defdW)) {
1473 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1476 WCMD_parameter (p, 2+negate, &command);
1478 else if ((s = strstrW (p, eqeqW))) {
1480 if (strstrW (quals, parmI) == NULL) {
1481 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1484 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1486 WCMD_parameter (s, 1, &command);
1489 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1493 /* Process rest of IF statement which is on the same line
1494 Note: This may process all or some of the cmdList (eg a GOTO) */
1495 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1498 /****************************************************************************
1501 * Move a file, directory tree or wildcarded set of files.
1504 void WCMD_move (void) {
1507 WIN32_FIND_DATAW fd;
1509 WCHAR input[MAX_PATH];
1510 WCHAR output[MAX_PATH];
1512 WCHAR dir[MAX_PATH];
1513 WCHAR fname[MAX_PATH];
1514 WCHAR ext[MAX_PATH];
1516 if (param1[0] == 0x00) {
1517 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1521 /* If no destination supplied, assume current directory */
1522 if (param2[0] == 0x00) {
1523 strcpyW(param2, dotW);
1526 /* If 2nd parm is directory, then use original filename */
1527 /* Convert partial path to full path */
1528 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1529 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1530 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1531 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1533 /* Split into components */
1534 WCMD_splitpath(input, drive, dir, fname, ext);
1536 hff = FindFirstFileW(input, &fd);
1537 while (hff != INVALID_HANDLE_VALUE) {
1538 WCHAR dest[MAX_PATH];
1539 WCHAR src[MAX_PATH];
1542 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1544 /* Build src & dest name */
1545 strcpyW(src, drive);
1548 /* See if dest is an existing directory */
1549 attribs = GetFileAttributesW(output);
1550 if (attribs != INVALID_FILE_ATTRIBUTES &&
1551 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1552 strcpyW(dest, output);
1553 strcatW(dest, slashW);
1554 strcatW(dest, fd.cFileName);
1556 strcpyW(dest, output);
1559 strcatW(src, fd.cFileName);
1561 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1562 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1564 /* Check if file is read only, otherwise move it */
1565 attribs = GetFileAttributesW(src);
1566 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1567 (attribs & FILE_ATTRIBUTE_READONLY)) {
1568 SetLastError(ERROR_ACCESS_DENIED);
1573 /* If destination exists, prompt unless /Y supplied */
1574 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1576 WCHAR copycmd[MAXSTRING];
1579 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1580 if (strstrW (quals, parmNoY))
1582 else if (strstrW (quals, parmY))
1585 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1586 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1587 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1588 && ! lstrcmpiW (copycmd, parmY));
1591 /* Prompt if overwriting */
1593 WCHAR question[MAXSTRING];
1596 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1598 /* Ask for confirmation */
1599 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1600 ok = WCMD_ask_confirm(question, FALSE, NULL);
1602 /* So delete the destination prior to the move */
1604 if (!DeleteFileW(dest)) {
1605 WCMD_print_error ();
1614 status = MoveFileW(src, dest);
1616 status = 1; /* Anything other than 0 to prevent error msg below */
1621 WCMD_print_error ();
1625 /* Step on to next match */
1626 if (FindNextFileW(hff, &fd) == 0) {
1628 hff = INVALID_HANDLE_VALUE;
1634 /****************************************************************************
1637 * Wait for keyboard input.
1640 void WCMD_pause (void) {
1645 WCMD_output (anykey);
1646 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1647 sizeof(string)/sizeof(WCHAR), &count, NULL);
1650 /****************************************************************************
1653 * Delete a directory.
1656 void WCMD_remove_dir (WCHAR *command) {
1659 int argsProcessed = 0;
1660 WCHAR *argN = command;
1661 static const WCHAR parmS[] = {'/','S','\0'};
1662 static const WCHAR parmQ[] = {'/','Q','\0'};
1664 /* Loop through all args */
1666 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1667 if (argN && argN[0] != '/') {
1668 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1669 wine_dbgstr_w(quals));
1672 /* If subdirectory search not supplied, just try to remove
1673 and report error if it fails (eg if it contains a file) */
1674 if (strstrW (quals, parmS) == NULL) {
1675 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1677 /* Otherwise use ShFileOp to recursively remove a directory */
1680 SHFILEOPSTRUCTW lpDir;
1683 if (strstrW (quals, parmQ) == NULL) {
1685 WCHAR question[MAXSTRING];
1686 static const WCHAR fmt[] = {'%','s',' ','\0'};
1688 /* Ask for confirmation */
1689 wsprintfW(question, fmt, thisArg);
1690 ok = WCMD_ask_confirm(question, TRUE, NULL);
1692 /* Abort if answer is 'N' */
1699 lpDir.pFrom = thisArg;
1700 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1701 lpDir.wFunc = FO_DELETE;
1702 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1707 /* Handle no valid args */
1708 if (argsProcessed == 0) {
1709 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1715 /****************************************************************************
1721 void WCMD_rename (void) {
1725 WIN32_FIND_DATAW fd;
1726 WCHAR input[MAX_PATH];
1727 WCHAR *dotDst = NULL;
1729 WCHAR dir[MAX_PATH];
1730 WCHAR fname[MAX_PATH];
1731 WCHAR ext[MAX_PATH];
1736 /* Must be at least two args */
1737 if (param1[0] == 0x00 || param2[0] == 0x00) {
1738 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1743 /* Destination cannot contain a drive letter or directory separator */
1744 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1745 SetLastError(ERROR_INVALID_PARAMETER);
1751 /* Convert partial path to full path */
1752 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1753 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1754 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1755 dotDst = strchrW(param2, '.');
1757 /* Split into components */
1758 WCMD_splitpath(input, drive, dir, fname, ext);
1760 hff = FindFirstFileW(input, &fd);
1761 while (hff != INVALID_HANDLE_VALUE) {
1762 WCHAR dest[MAX_PATH];
1763 WCHAR src[MAX_PATH];
1764 WCHAR *dotSrc = NULL;
1767 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1769 /* FIXME: If dest name or extension is *, replace with filename/ext
1770 part otherwise use supplied name. This supports:
1772 ren jim.* fred.* etc
1773 However, windows has a more complex algorithm supporting eg
1774 ?'s and *'s mid name */
1775 dotSrc = strchrW(fd.cFileName, '.');
1777 /* Build src & dest name */
1778 strcpyW(src, drive);
1781 dirLen = strlenW(src);
1782 strcatW(src, fd.cFileName);
1785 if (param2[0] == '*') {
1786 strcatW(dest, fd.cFileName);
1787 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1789 strcatW(dest, param2);
1790 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1793 /* Build Extension */
1794 if (dotDst && (*(dotDst+1)=='*')) {
1795 if (dotSrc) strcatW(dest, dotSrc);
1796 } else if (dotDst) {
1797 if (dotDst) strcatW(dest, dotDst);
1800 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1801 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1803 /* Check if file is read only, otherwise move it */
1804 attribs = GetFileAttributesW(src);
1805 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1806 (attribs & FILE_ATTRIBUTE_READONLY)) {
1807 SetLastError(ERROR_ACCESS_DENIED);
1810 status = MoveFileW(src, dest);
1814 WCMD_print_error ();
1818 /* Step on to next match */
1819 if (FindNextFileW(hff, &fd) == 0) {
1821 hff = INVALID_HANDLE_VALUE;
1827 /*****************************************************************************
1830 * Make a copy of the environment.
1832 static WCHAR *WCMD_dupenv( const WCHAR *env )
1842 len += (strlenW(&env[len]) + 1);
1844 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1847 WINE_ERR("out of memory\n");
1850 memcpy (env_copy, env, len*sizeof (WCHAR));
1856 /*****************************************************************************
1859 * setlocal pushes the environment onto a stack
1860 * Save the environment as unicode so we don't screw anything up.
1862 void WCMD_setlocal (const WCHAR *s) {
1864 struct env_stack *env_copy;
1865 WCHAR cwd[MAX_PATH];
1867 /* DISABLEEXTENSIONS ignored */
1869 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1872 WINE_ERR ("out of memory\n");
1876 env = GetEnvironmentStringsW ();
1878 env_copy->strings = WCMD_dupenv (env);
1879 if (env_copy->strings)
1881 env_copy->next = saved_environment;
1882 saved_environment = env_copy;
1884 /* Save the current drive letter */
1885 GetCurrentDirectoryW(MAX_PATH, cwd);
1886 env_copy->u.cwd = cwd[0];
1889 LocalFree (env_copy);
1891 FreeEnvironmentStringsW (env);
1895 /*****************************************************************************
1898 * endlocal pops the environment off a stack
1899 * Note: When searching for '=', search from WCHAR position 1, to handle
1900 * special internal environment variables =C:, =D: etc
1902 void WCMD_endlocal (void) {
1903 WCHAR *env, *old, *p;
1904 struct env_stack *temp;
1907 if (!saved_environment)
1910 /* pop the old environment from the stack */
1911 temp = saved_environment;
1912 saved_environment = temp->next;
1914 /* delete the current environment, totally */
1915 env = GetEnvironmentStringsW ();
1916 old = WCMD_dupenv (GetEnvironmentStringsW ());
1919 n = strlenW(&old[len]) + 1;
1920 p = strchrW(&old[len] + 1, '=');
1924 SetEnvironmentVariableW (&old[len], NULL);
1929 FreeEnvironmentStringsW (env);
1931 /* restore old environment */
1932 env = temp->strings;
1935 n = strlenW(&env[len]) + 1;
1936 p = strchrW(&env[len] + 1, '=');
1940 SetEnvironmentVariableW (&env[len], p);
1945 /* Restore current drive letter */
1946 if (IsCharAlphaW(temp->u.cwd)) {
1948 WCHAR cwd[MAX_PATH];
1949 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1951 wsprintfW(envvar, fmt, temp->u.cwd);
1952 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1953 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1954 SetCurrentDirectoryW(cwd);
1962 /*****************************************************************************
1963 * WCMD_setshow_attrib
1965 * Display and optionally sets DOS attributes on a file or directory
1969 void WCMD_setshow_attrib (void) {
1973 WIN32_FIND_DATAW fd;
1974 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1975 WCHAR *name = param1;
1977 DWORD attrib_clear=0;
1979 if (param1[0] == '+' || param1[0] == '-') {
1981 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
1982 switch (param1[1]) {
1983 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
1984 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
1985 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
1986 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
1988 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1991 switch (param1[0]) {
1992 case '+': attrib_set = attrib; break;
1993 case '-': attrib_clear = attrib; break;
1998 if (strlenW(name) == 0) {
1999 static const WCHAR slashStarW[] = {'\\','*','\0'};
2001 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
2002 strcatW (name, slashStarW);
2005 hff = FindFirstFileW(name, &fd);
2006 if (hff == INVALID_HANDLE_VALUE) {
2007 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
2011 if (attrib_set || attrib_clear) {
2012 fd.dwFileAttributes &= ~attrib_clear;
2013 fd.dwFileAttributes |= attrib_set;
2014 if (!fd.dwFileAttributes)
2015 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
2016 SetFileAttributesW(name, fd.dwFileAttributes);
2018 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
2019 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
2022 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
2025 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
2028 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
2031 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
2034 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
2037 WCMD_output (fmt, flags, fd.cFileName);
2038 for (count=0; count < 8; count++) flags[count] = ' ';
2040 } while (FindNextFileW(hff, &fd) != 0);
2045 /*****************************************************************************
2046 * WCMD_setshow_default
2048 * Set/Show the current default directory
2051 void WCMD_setshow_default (WCHAR *command) {
2057 WIN32_FIND_DATAW fd;
2059 static const WCHAR parmD[] = {'/','D','\0'};
2061 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2063 /* Skip /D and trailing whitespace if on the front of the command line */
2064 if (CompareStringW(LOCALE_USER_DEFAULT,
2065 NORM_IGNORECASE | SORT_STRINGSORT,
2066 command, 2, parmD, -1) == 2) {
2068 while (*command && *command==' ') command++;
2071 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2072 if (strlenW(command) == 0) {
2073 strcatW (cwd, newline);
2077 /* Remove any double quotes, which may be in the
2078 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2081 if (*command != '"') *pos++ = *command;
2086 /* Search for appropriate directory */
2087 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2088 hff = FindFirstFileW(string, &fd);
2089 while (hff != INVALID_HANDLE_VALUE) {
2090 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2091 WCHAR fpath[MAX_PATH];
2093 WCHAR dir[MAX_PATH];
2094 WCHAR fname[MAX_PATH];
2095 WCHAR ext[MAX_PATH];
2096 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2098 /* Convert path into actual directory spec */
2099 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2100 WCMD_splitpath(fpath, drive, dir, fname, ext);
2103 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2106 hff = INVALID_HANDLE_VALUE;
2110 /* Step on to next match */
2111 if (FindNextFileW(hff, &fd) == 0) {
2113 hff = INVALID_HANDLE_VALUE;
2118 /* Change to that directory */
2119 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2121 status = SetCurrentDirectoryW(string);
2124 WCMD_print_error ();
2128 /* Save away the actual new directory, to store as current location */
2129 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2131 /* Restore old directory if drive letter would change, and
2132 CD x:\directory /D (or pushd c:\directory) not supplied */
2133 if ((strstrW(quals, parmD) == NULL) &&
2134 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2135 SetCurrentDirectoryW(cwd);
2139 /* Set special =C: type environment variable, for drive letter of
2140 change of directory, even if path was restored due to missing
2141 /D (allows changing drive letter when not resident on that
2143 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2145 strcpyW(env, equalW);
2146 memcpy(env+1, string, 2 * sizeof(WCHAR));
2148 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2149 SetEnvironmentVariableW(env, string);
2156 /****************************************************************************
2159 * Set/Show the system date
2160 * FIXME: Can't change date yet
2163 void WCMD_setshow_date (void) {
2165 WCHAR curdate[64], buffer[64];
2167 static const WCHAR parmT[] = {'/','T','\0'};
2169 if (strlenW(param1) == 0) {
2170 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2171 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2172 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2173 if (strstrW (quals, parmT) == NULL) {
2174 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2175 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2176 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2178 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2182 else WCMD_print_error ();
2185 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2189 /****************************************************************************
2192 static int WCMD_compare( const void *a, const void *b )
2195 const WCHAR * const *str_a = a, * const *str_b = b;
2196 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2197 *str_a, -1, *str_b, -1 );
2198 if( r == CSTR_LESS_THAN ) return -1;
2199 if( r == CSTR_GREATER_THAN ) return 1;
2203 /****************************************************************************
2204 * WCMD_setshow_sortenv
2206 * sort variables into order for display
2207 * Optionally only display those who start with a stub
2208 * returns the count displayed
2210 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2212 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2215 if (stub) stublen = strlenW(stub);
2217 /* count the number of strings, and the total length */
2219 len += (strlenW(&s[len]) + 1);
2223 /* add the strings to an array */
2224 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2228 for( i=1; i<count; i++ )
2229 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2231 /* sort the array */
2232 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2235 for( i=0; i<count; i++ ) {
2236 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2237 NORM_IGNORECASE | SORT_STRINGSORT,
2238 str[i], stublen, stub, -1) == 2) {
2239 /* Don't display special internal variables */
2240 if (str[i][0] != '=') {
2241 WCMD_output_asis(str[i]);
2242 WCMD_output_asis(newline);
2249 return displayedcount;
2252 /****************************************************************************
2255 * Set/Show the environment variables
2258 void WCMD_setshow_env (WCHAR *s) {
2263 static const WCHAR parmP[] = {'/','P','\0'};
2265 if (param1[0] == 0x00 && quals[0] == 0x00) {
2266 env = GetEnvironmentStringsW();
2267 WCMD_setshow_sortenv( env, NULL );
2271 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2272 if (CompareStringW(LOCALE_USER_DEFAULT,
2273 NORM_IGNORECASE | SORT_STRINGSORT,
2274 s, 2, parmP, -1) == 2) {
2275 WCHAR string[MAXSTRING];
2279 while (*s && *s==' ') s++;
2281 WCMD_opt_s_strip_quotes(s);
2283 /* If no parameter, or no '=' sign, return an error */
2284 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2285 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2289 /* Output the prompt */
2291 if (strlenW(p) != 0) WCMD_output(p);
2293 /* Read the reply */
2294 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2295 sizeof(string)/sizeof(WCHAR), &count, NULL);
2297 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2298 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2299 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2300 wine_dbgstr_w(string));
2301 status = SetEnvironmentVariableW(s, string);
2308 WCMD_opt_s_strip_quotes(s);
2309 p = strchrW (s, '=');
2311 env = GetEnvironmentStringsW();
2312 if (WCMD_setshow_sortenv( env, s ) == 0) {
2313 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2320 if (strlenW(p) == 0) p = NULL;
2321 status = SetEnvironmentVariableW(s, p);
2322 gle = GetLastError();
2323 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2325 } else if ((!status)) WCMD_print_error();
2329 /****************************************************************************
2332 * Set/Show the path environment variable
2335 void WCMD_setshow_path (WCHAR *command) {
2339 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2340 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2342 if (strlenW(param1) == 0) {
2343 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2345 WCMD_output_asis ( pathEqW);
2346 WCMD_output_asis ( string);
2347 WCMD_output_asis ( newline);
2350 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2354 if (*command == '=') command++; /* Skip leading '=' */
2355 status = SetEnvironmentVariableW(pathW, command);
2356 if (!status) WCMD_print_error();
2360 /****************************************************************************
2361 * WCMD_setshow_prompt
2363 * Set or show the command prompt.
2366 void WCMD_setshow_prompt (void) {
2369 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2371 if (strlenW(param1) == 0) {
2372 SetEnvironmentVariableW(promptW, NULL);
2376 while ((*s == '=') || (*s == ' ')) s++;
2377 if (strlenW(s) == 0) {
2378 SetEnvironmentVariableW(promptW, NULL);
2380 else SetEnvironmentVariableW(promptW, s);
2384 /****************************************************************************
2387 * Set/Show the system time
2388 * FIXME: Can't change time yet
2391 void WCMD_setshow_time (void) {
2393 WCHAR curtime[64], buffer[64];
2396 static const WCHAR parmT[] = {'/','T','\0'};
2398 if (strlenW(param1) == 0) {
2400 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2401 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2402 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2403 if (strstrW (quals, parmT) == NULL) {
2404 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2405 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2406 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2408 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2412 else WCMD_print_error ();
2415 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2419 /****************************************************************************
2422 * Shift batch parameters.
2423 * Optional /n says where to start shifting (n=0-8)
2426 void WCMD_shift (WCHAR *command) {
2429 if (context != NULL) {
2430 WCHAR *pos = strchrW(command, '/');
2435 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2436 start = (*(pos+1) - '0');
2438 SetLastError(ERROR_INVALID_PARAMETER);
2443 WINE_TRACE("Shifting variables, starting at %d\n", start);
2444 for (i=start;i<=8;i++) {
2445 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2447 context -> shift_count[9] = context -> shift_count[9] + 1;
2452 /****************************************************************************
2455 * Set the console title
2457 void WCMD_title (WCHAR *command) {
2458 SetConsoleTitleW(command);
2461 /****************************************************************************
2464 * Copy a file to standard output.
2467 void WCMD_type (WCHAR *command) {
2470 WCHAR *argN = command;
2471 BOOL writeHeaders = FALSE;
2473 if (param1[0] == 0x00) {
2474 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2478 if (param2[0] != 0x00) writeHeaders = TRUE;
2480 /* Loop through all args */
2483 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2491 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2492 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2493 FILE_ATTRIBUTE_NORMAL, NULL);
2494 if (h == INVALID_HANDLE_VALUE) {
2495 WCMD_print_error ();
2496 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2500 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2501 WCMD_output(fmt, thisArg);
2503 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2504 if (count == 0) break; /* ReadFile reports success on EOF! */
2506 WCMD_output_asis (buffer);
2510 WCMD_output_asis (newline);
2515 /****************************************************************************
2518 * Output either a file or stdin to screen in pages
2521 void WCMD_more (WCHAR *command) {
2524 WCHAR *argN = command;
2526 WCHAR moreStrPage[100];
2529 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2530 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2531 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2532 ')',' ','-','-','\n','\0'};
2533 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2535 /* Prefix the NLS more with '-- ', then load the text */
2537 strcpyW(moreStr, moreStart);
2538 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2539 (sizeof(moreStr)/sizeof(WCHAR))-3);
2541 if (param1[0] == 0x00) {
2543 /* Wine implements pipes via temporary files, and hence stdin is
2544 effectively reading from the file. This means the prompts for
2545 more are satisfied by the next line from the input (file). To
2546 avoid this, ensure stdin is to the console */
2547 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2548 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2549 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2550 FILE_ATTRIBUTE_NORMAL, 0);
2551 WINE_TRACE("No parms - working probably in pipe mode\n");
2552 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2554 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2555 once you get in this bit unless due to a pipe, its going to end badly... */
2556 wsprintfW(moreStrPage, moreFmt, moreStr);
2558 WCMD_enter_paged_mode(moreStrPage);
2559 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2560 if (count == 0) break; /* ReadFile reports success on EOF! */
2562 WCMD_output_asis (buffer);
2564 WCMD_leave_paged_mode();
2566 /* Restore stdin to what it was */
2567 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2568 CloseHandle(hConIn);
2572 BOOL needsPause = FALSE;
2574 /* Loop through all args */
2575 WINE_TRACE("Parms supplied - working through each file\n");
2576 WCMD_enter_paged_mode(moreStrPage);
2579 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2587 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2588 WCMD_leave_paged_mode();
2589 WCMD_output_asis(moreStrPage);
2590 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2591 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2592 WCMD_enter_paged_mode(moreStrPage);
2596 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2597 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2598 FILE_ATTRIBUTE_NORMAL, NULL);
2599 if (h == INVALID_HANDLE_VALUE) {
2600 WCMD_print_error ();
2601 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2605 ULONG64 fileLen = 0;
2606 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2608 /* Get the file size */
2609 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2610 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2613 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2614 if (count == 0) break; /* ReadFile reports success on EOF! */
2618 /* Update % count (would be used in WCMD_output_asis as prompt) */
2619 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2621 WCMD_output_asis (buffer);
2627 WCMD_leave_paged_mode();
2631 /****************************************************************************
2634 * Display verify flag.
2635 * FIXME: We don't actually do anything with the verify flag other than toggle
2639 void WCMD_verify (WCHAR *command) {
2643 count = strlenW(command);
2645 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2646 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2649 if (lstrcmpiW(command, onW) == 0) {
2653 else if (lstrcmpiW(command, offW) == 0) {
2657 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2660 /****************************************************************************
2663 * Display version info.
2666 void WCMD_version (void) {
2668 WCMD_output (version_string);
2672 /****************************************************************************
2675 * Display volume info and/or set volume label. Returns 0 if error.
2678 int WCMD_volume (int mode, WCHAR *path) {
2680 DWORD count, serial;
2681 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2684 if (strlenW(path) == 0) {
2685 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2687 WCMD_print_error ();
2690 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2691 &serial, NULL, NULL, NULL, 0);
2694 static const WCHAR fmt[] = {'%','s','\\','\0'};
2695 if ((path[1] != ':') || (strlenW(path) != 2)) {
2696 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2699 wsprintfW (curdir, fmt, path);
2700 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2705 WCMD_print_error ();
2708 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2709 curdir[0], label, HIWORD(serial), LOWORD(serial));
2711 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2712 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2713 sizeof(string)/sizeof(WCHAR), &count, NULL);
2715 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2716 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2718 if (strlenW(path) != 0) {
2719 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2722 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2728 /**************************************************************************
2731 * Exit either the process, or just this batch program
2735 void WCMD_exit (CMD_LIST **cmdList) {
2737 static const WCHAR parmB[] = {'/','B','\0'};
2738 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2740 if (context && lstrcmpiW(quals, parmB) == 0) {
2742 context -> skip_rest = TRUE;
2750 /*****************************************************************************
2753 * Lists or sets file associations (assoc = TRUE)
2754 * Lists or sets file types (assoc = FALSE)
2756 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2759 DWORD accessOptions = KEY_READ;
2761 LONG rc = ERROR_SUCCESS;
2762 WCHAR keyValue[MAXSTRING];
2763 DWORD valueLen = MAXSTRING;
2765 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2766 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2768 /* See if parameter includes '=' */
2770 newValue = strchrW(command, '=');
2771 if (newValue) accessOptions |= KEY_WRITE;
2773 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2774 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2775 accessOptions, &key) != ERROR_SUCCESS) {
2776 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2780 /* If no parameters then list all associations */
2781 if (*command == 0x00) {
2784 /* Enumerate all the keys */
2785 while (rc != ERROR_NO_MORE_ITEMS) {
2786 WCHAR keyName[MAXSTRING];
2789 /* Find the next value */
2790 nameLen = MAXSTRING;
2791 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2793 if (rc == ERROR_SUCCESS) {
2795 /* Only interested in extension ones if assoc, or others
2797 if ((keyName[0] == '.' && assoc) ||
2798 (!(keyName[0] == '.') && (!assoc)))
2800 WCHAR subkey[MAXSTRING];
2801 strcpyW(subkey, keyName);
2802 if (!assoc) strcatW(subkey, shOpCmdW);
2804 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2806 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2807 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2808 WCMD_output_asis(keyName);
2809 WCMD_output_asis(equalW);
2810 /* If no default value found, leave line empty after '=' */
2811 if (rc == ERROR_SUCCESS) {
2812 WCMD_output_asis(keyValue);
2814 WCMD_output_asis(newline);
2815 RegCloseKey(readKey);
2823 /* Parameter supplied - if no '=' on command line, its a query */
2824 if (newValue == NULL) {
2826 WCHAR subkey[MAXSTRING];
2828 /* Query terminates the parameter at the first space */
2829 strcpyW(keyValue, command);
2830 space = strchrW(keyValue, ' ');
2831 if (space) *space=0x00;
2833 /* Set up key name */
2834 strcpyW(subkey, keyValue);
2835 if (!assoc) strcatW(subkey, shOpCmdW);
2837 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2839 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2840 WCMD_output_asis(command);
2841 WCMD_output_asis(equalW);
2842 /* If no default value found, leave line empty after '=' */
2843 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2844 WCMD_output_asis(newline);
2845 RegCloseKey(readKey);
2848 WCHAR msgbuffer[MAXSTRING];
2849 WCHAR outbuffer[MAXSTRING];
2851 /* Load the translated 'File association not found' */
2853 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2855 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2857 wsprintfW(outbuffer, msgbuffer, keyValue);
2858 WCMD_output_asis(outbuffer);
2862 /* Not a query - its a set or clear of a value */
2865 WCHAR subkey[MAXSTRING];
2867 /* Get pointer to new value */
2871 /* Set up key name */
2872 strcpyW(subkey, command);
2873 if (!assoc) strcatW(subkey, shOpCmdW);
2875 /* If nothing after '=' then clear value - only valid for ASSOC */
2876 if (*newValue == 0x00) {
2878 if (assoc) rc = RegDeleteKeyW(key, command);
2879 if (assoc && rc == ERROR_SUCCESS) {
2880 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2882 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2887 WCHAR msgbuffer[MAXSTRING];
2888 WCHAR outbuffer[MAXSTRING];
2890 /* Load the translated 'File association not found' */
2892 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2893 sizeof(msgbuffer)/sizeof(WCHAR));
2895 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2896 sizeof(msgbuffer)/sizeof(WCHAR));
2898 wsprintfW(outbuffer, msgbuffer, keyValue);
2899 WCMD_output_asis(outbuffer);
2903 /* It really is a set value = contents */
2905 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2906 accessOptions, NULL, &readKey, NULL);
2907 if (rc == ERROR_SUCCESS) {
2908 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2909 (LPBYTE)newValue, strlenW(newValue));
2910 RegCloseKey(readKey);
2913 if (rc != ERROR_SUCCESS) {
2917 WCMD_output_asis(command);
2918 WCMD_output_asis(equalW);
2919 WCMD_output_asis(newValue);
2920 WCMD_output_asis(newline);
2930 /****************************************************************************
2933 * Clear the terminal screen.
2936 void WCMD_color (void) {
2938 /* Emulate by filling the screen from the top left to bottom right with
2939 spaces, then moving the cursor to the top left afterwards */
2940 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2941 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2943 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2944 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2948 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2954 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2959 /* Convert the color hex digits */
2960 if (param1[0] == 0x00) {
2961 color = defaultColor;
2963 color = strtoulW(param1, NULL, 16);
2966 /* Fail if fg == bg color */
2967 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2972 /* Set the current screen contents and ensure all future writes
2973 remain this color */
2974 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2975 SetConsoleTextAttribute(hStdOut, color);