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 /* If filename part of parameter is * or *.*,
583 * and neither /Q nor /P options were given,
584 * prompt the user whether to proceed.
585 * Returns FALSE if user says no, TRUE otherwise.
586 * *pPrompted is set to TRUE if the user is prompted.
587 * (If /P supplied, del will prompt for individual files later.)
589 static BOOL WCMD_delete_confirm_wildcard(WCHAR *filename, BOOL *pPrompted) {
590 static const WCHAR parmP[] = {'/','P','\0'};
591 static const WCHAR parmQ[] = {'/','Q','\0'};
593 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
594 static const WCHAR anyExt[]= {'.','*','\0'};
597 WCHAR fname[MAX_PATH];
599 WCHAR fpath[MAX_PATH];
601 /* Convert path into actual directory spec */
602 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
603 WCMD_splitpath(fpath, drive, dir, fname, ext);
605 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
606 if ((strcmpW(fname, starW) == 0) &&
607 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
609 WCHAR question[MAXSTRING];
610 static const WCHAR fmt[] = {'%','s',' ','\0'};
612 /* Caller uses this to suppress "file not found" warning later */
615 /* Ask for confirmation */
616 wsprintfW(question, fmt, fpath);
617 return WCMD_ask_confirm(question, TRUE, NULL);
620 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
624 /* Helper function for WCMD_delete().
625 * Deletes a single file, directory, or wildcard.
626 * If /S was given, does it recursively.
627 * Returns TRUE if a file was deleted.
629 static BOOL WCMD_delete_one (WCHAR *thisArg) {
631 static const WCHAR parmP[] = {'/','P','\0'};
632 static const WCHAR parmS[] = {'/','S','\0'};
633 static const WCHAR parmF[] = {'/','F','\0'};
635 DWORD unwanted_attrs;
637 WCHAR argCopy[MAX_PATH];
640 WCHAR fpath[MAX_PATH];
642 BOOL handleParm = TRUE;
644 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
646 strcpyW(argCopy, thisArg);
647 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
648 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
650 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
651 /* Skip this arg if user declines to delete *.* */
655 /* First, try to delete in the current directory */
656 hff = FindFirstFileW(argCopy, &fd);
657 if (hff == INVALID_HANDLE_VALUE) {
663 /* Support del <dirname> by just deleting all files dirname\* */
665 && (strchrW(argCopy,'*') == NULL)
666 && (strchrW(argCopy,'?') == NULL)
667 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
669 WCHAR modifiedParm[MAX_PATH];
670 static const WCHAR slashStar[] = {'\\','*','\0'};
672 strcpyW(modifiedParm, argCopy);
673 strcatW(modifiedParm, slashStar);
676 WCMD_delete_one(modifiedParm);
678 } else if (handleParm) {
680 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
681 strcpyW (fpath, argCopy);
683 p = strrchrW (fpath, '\\');
686 strcatW (fpath, fd.cFileName);
688 else strcpyW (fpath, fd.cFileName);
689 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
692 /* Handle attribute matching (/A) */
693 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
694 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
696 /* /P means prompt for each file */
697 if (ok && strstrW (quals, parmP) != NULL) {
698 WCHAR question[MAXSTRING];
700 /* Ask for confirmation */
701 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
702 ok = WCMD_ask_confirm(question, FALSE, NULL);
705 /* Only proceed if ok to */
708 /* If file is read only, and /A:r or /F supplied, delete it */
709 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
710 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
711 strstrW (quals, parmF) != NULL)) {
712 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
715 /* Now do the delete */
716 if (!DeleteFileW(fpath)) WCMD_print_error ();
720 } while (FindNextFileW(hff, &fd) != 0);
724 /* Now recurse into all subdirectories handling the parameter in the same way */
725 if (strstrW (quals, parmS) != NULL) {
727 WCHAR thisDir[MAX_PATH];
732 WCHAR fname[MAX_PATH];
735 /* Convert path into actual directory spec */
736 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
737 WCMD_splitpath(thisDir, drive, dir, fname, ext);
739 strcpyW(thisDir, drive);
740 strcatW(thisDir, dir);
741 cPos = strlenW(thisDir);
743 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
745 /* Append '*' to the directory */
747 thisDir[cPos+1] = 0x00;
749 hff = FindFirstFileW(thisDir, &fd);
751 /* Remove residual '*' */
752 thisDir[cPos] = 0x00;
754 if (hff != INVALID_HANDLE_VALUE) {
755 DIRECTORY_STACK *allDirs = NULL;
756 DIRECTORY_STACK *lastEntry = NULL;
759 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
760 (strcmpW(fd.cFileName, dotdotW) != 0) &&
761 (strcmpW(fd.cFileName, dotW) != 0)) {
763 DIRECTORY_STACK *nextDir;
764 WCHAR subParm[MAX_PATH];
766 /* Work out search parameter in sub dir */
767 strcpyW (subParm, thisDir);
768 strcatW (subParm, fd.cFileName);
769 strcatW (subParm, slashW);
770 strcatW (subParm, fname);
771 strcatW (subParm, ext);
772 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
774 /* Allocate memory, add to list */
775 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
776 if (allDirs == NULL) allDirs = nextDir;
777 if (lastEntry != NULL) lastEntry->next = nextDir;
779 nextDir->next = NULL;
780 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
781 (strlenW(subParm)+1) * sizeof(WCHAR));
782 strcpyW(nextDir->dirName, subParm);
784 } while (FindNextFileW(hff, &fd) != 0);
787 /* Go through each subdir doing the delete */
788 while (allDirs != NULL) {
789 DIRECTORY_STACK *tempDir;
791 tempDir = allDirs->next;
792 found |= WCMD_delete_one (allDirs->dirName);
794 HeapFree(GetProcessHeap(),0,allDirs->dirName);
795 HeapFree(GetProcessHeap(),0,allDirs);
804 /****************************************************************************
807 * Delete a file or wildcarded set.
810 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
811 * - Each set is a pattern, eg /ahr /as-r means
812 * readonly+hidden OR nonreadonly system files
813 * - The '-' applies to a single field, ie /a:-hr means read only
817 BOOL WCMD_delete (WCHAR *command) {
820 BOOL argsProcessed = FALSE;
821 BOOL foundAny = FALSE;
825 for (argno=0; ; argno++) {
830 thisArg = WCMD_parameter (command, argno, &argN);
832 break; /* no more parameters */
834 continue; /* skip options */
836 argsProcessed = TRUE;
837 found = WCMD_delete_one(thisArg);
840 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
845 /* Handle no valid args */
847 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
852 /****************************************************************************
855 * Echo input to the screen (or not). We don't try to emulate the bugs
856 * in DOS (try typing "ECHO ON AGAIN" for an example).
859 void WCMD_echo (const WCHAR *command) {
862 const WCHAR *origcommand = command;
864 if (command[0]==' ' || command[0]=='.')
866 count = strlenW(command);
867 if (count == 0 && origcommand[0]!='.') {
868 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
869 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
872 if (lstrcmpiW(command, onW) == 0) {
876 if (lstrcmpiW(command, offW) == 0) {
880 WCMD_output_asis (command);
881 WCMD_output (newline);
885 /**************************************************************************
888 * Batch file loop processing.
890 * On entry: cmdList contains the syntax up to the set
891 * next cmdList and all in that bracket contain the set data
892 * next cmdlist contains the DO cmd
893 * following that is either brackets or && entries (as per if)
897 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
902 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
903 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
904 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
910 BOOL expandDirs = FALSE;
911 BOOL useNumbers = FALSE;
912 BOOL doFileset = FALSE;
913 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
915 CMD_LIST *thisCmdStart;
918 /* Handle optional qualifiers (multiple are allowed) */
919 while (*curPos && *curPos == '/') {
920 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
922 switch (toupperW(*curPos)) {
923 case 'D': curPos++; expandDirs = TRUE; break;
924 case 'L': curPos++; useNumbers = TRUE; break;
926 /* Recursive is special case - /R can have an optional path following it */
927 /* filenamesets are another special case - /F can have an optional options following it */
931 BOOL isRecursive = (*curPos == 'R');
936 /* Skip whitespace */
938 while (*curPos && *curPos==' ') curPos++;
940 /* Next parm is either qualifier, path/options or variable -
941 only care about it if it is the path/options */
942 if (*curPos && *curPos != '/' && *curPos != '%') {
943 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
944 else WINE_FIXME("/F needs to handle options\n");
949 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
953 /* Skip whitespace between qualifiers */
954 while (*curPos && *curPos==' ') curPos++;
957 /* Skip whitespace before variable */
958 while (*curPos && *curPos==' ') curPos++;
960 /* Ensure line continues with variable */
961 if (!*curPos || *curPos != '%') {
962 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
966 /* Variable should follow */
968 while (curPos[i] && curPos[i]!=' ') i++;
969 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
971 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
974 /* Skip whitespace before IN */
975 while (*curPos && *curPos==' ') curPos++;
977 /* Ensure line continues with IN */
978 if (!*curPos || lstrcmpiW (curPos, inW)) {
979 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
983 /* Save away where the set of data starts and the variable */
984 thisDepth = (*cmdList)->bracketDepth;
985 *cmdList = (*cmdList)->nextcommand;
986 setStart = (*cmdList);
988 /* Skip until the close bracket */
989 WINE_TRACE("Searching %p as the set\n", *cmdList);
991 (*cmdList)->command != NULL &&
992 (*cmdList)->bracketDepth > thisDepth) {
993 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
994 *cmdList = (*cmdList)->nextcommand;
997 /* Skip the close bracket, if there is one */
998 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1000 /* Syntax error if missing close bracket, or nothing following it
1001 and once we have the complete set, we expect a DO */
1002 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
1003 if ((*cmdList == NULL) ||
1004 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1005 (*cmdList)->command, 3, doW, -1) != 2)) {
1006 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1010 /* Save away the starting position for the commands (and offset for the
1012 cmdStart = *cmdList;
1014 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1018 /* Loop through all set entries */
1020 thisSet->command != NULL &&
1021 thisSet->bracketDepth >= thisDepth) {
1023 /* Loop through all entries on the same line */
1027 WINE_TRACE("Processing for set %p\n", thisSet);
1029 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
1032 * If the parameter within the set has a wildcard then search for matching files
1033 * otherwise do a literal substitution.
1035 static const WCHAR wildcards[] = {'*','?','\0'};
1036 thisCmdStart = cmdStart;
1039 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1041 if (!useNumbers && !doFileset) {
1042 if (strpbrkW (item, wildcards)) {
1043 hff = FindFirstFileW(item, &fd);
1044 if (hff != INVALID_HANDLE_VALUE) {
1046 BOOL isDirectory = FALSE;
1048 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1050 /* Handle as files or dirs appropriately, but ignore . and .. */
1051 if (isDirectory == expandDirs &&
1052 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1053 (strcmpW(fd.cFileName, dotW) != 0))
1055 thisCmdStart = cmdStart;
1056 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1057 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1058 fd.cFileName, FALSE, TRUE);
1061 } while (FindNextFileW(hff, &fd) != 0);
1065 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1068 } else if (useNumbers) {
1069 /* Convert the first 3 numbers to signed longs and save */
1070 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1071 /* else ignore them! */
1073 /* Filesets - either a list of files, or a command to run and parse the output */
1074 } else if (doFileset && *itemStart != '"') {
1077 WCHAR temp_file[MAX_PATH];
1079 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1080 wine_dbgstr_w(item));
1082 /* If backquote or single quote, we need to launch that command
1083 and parse the results - use a temporary file */
1084 if (*itemStart == '`' || *itemStart == '\'') {
1086 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1087 static const WCHAR redirOut[] = {'>','%','s','\0'};
1088 static const WCHAR cmdW[] = {'C','M','D','\0'};
1090 /* Remove trailing character */
1091 itemStart[strlenW(itemStart)-1] = 0x00;
1093 /* Get temp filename */
1094 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1095 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1097 /* Execute program and redirect output */
1098 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1099 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1101 /* Open the file, read line by line and process */
1102 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1103 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1106 /* Open the file, read line by line and process */
1107 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1108 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1111 /* Process the input file */
1112 if (input == INVALID_HANDLE_VALUE) {
1113 WCMD_print_error ();
1114 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1116 return; /* FOR loop aborts at first failure here */
1120 WCHAR buffer[MAXSTRING] = {'\0'};
1121 WCHAR *where, *parm;
1123 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1125 /* Skip blank lines*/
1126 parm = WCMD_parameter (buffer, 0, &where);
1127 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1128 wine_dbgstr_w(buffer));
1131 /* FIXME: The following should be moved into its own routine and
1132 reused for the string literal parsing below */
1133 thisCmdStart = cmdStart;
1134 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1135 cmdEnd = thisCmdStart;
1141 CloseHandle (input);
1144 /* Delete the temporary file */
1145 if (*itemStart == '`' || *itemStart == '\'') {
1146 DeleteFileW(temp_file);
1149 /* Filesets - A string literal */
1150 } else if (doFileset && *itemStart == '"') {
1151 WCHAR buffer[MAXSTRING] = {'\0'};
1152 WCHAR *where, *parm;
1154 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1155 strcpyW(buffer, item);
1156 parm = WCMD_parameter (buffer, 0, &where);
1157 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1158 wine_dbgstr_w(buffer));
1161 /* FIXME: The following should be moved into its own routine and
1162 reused for the string literal parsing below */
1163 thisCmdStart = cmdStart;
1164 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1165 cmdEnd = thisCmdStart;
1169 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1170 cmdEnd = thisCmdStart;
1174 /* Move onto the next set line */
1175 thisSet = thisSet->nextcommand;
1178 /* If /L is provided, now run the for loop */
1181 static const WCHAR fmt[] = {'%','d','\0'};
1183 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1184 numbers[0], numbers[2], numbers[1]);
1186 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1189 sprintfW(thisNum, fmt, i);
1190 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1192 thisCmdStart = cmdStart;
1193 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1194 cmdEnd = thisCmdStart;
1198 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1199 all processing, OR it should be pointing to the end of && processing OR
1200 it should be pointing at the NULL end of bracket for the DO. The return
1201 value needs to be the NEXT command to execute, which it either is, or
1202 we need to step over the closing bracket */
1204 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1208 /*****************************************************************************
1211 * Execute a command, and any && or bracketed follow on to the command. The
1212 * first command to be executed may not be at the front of the
1213 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1215 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1216 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1218 CMD_LIST *curPosition = *cmdList;
1219 int myDepth = (*cmdList)->bracketDepth;
1221 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1222 cmdList, wine_dbgstr_w(firstcmd),
1223 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1226 /* Skip leading whitespace between condition and the command */
1227 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1229 /* Process the first command, if there is one */
1230 if (conditionTRUE && firstcmd && *firstcmd) {
1231 WCHAR *command = WCMD_strdupW(firstcmd);
1232 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1233 HeapFree(GetProcessHeap(), 0, command);
1237 /* If it didn't move the position, step to next command */
1238 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1240 /* Process any other parts of the command */
1242 BOOL processThese = TRUE;
1244 if (isIF) processThese = conditionTRUE;
1247 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1249 /* execute all appropriate commands */
1250 curPosition = *cmdList;
1252 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1254 (*cmdList)->prevDelim,
1255 (*cmdList)->bracketDepth, myDepth);
1257 /* Execute any statements appended to the line */
1258 /* FIXME: Only if previous call worked for && or failed for || */
1259 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1260 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1261 if (processThese && (*cmdList)->command) {
1262 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1265 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1267 /* Execute any appended to the statement with (...) */
1268 } else if ((*cmdList)->bracketDepth > myDepth) {
1270 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1271 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1273 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1275 /* End of the command - does 'ELSE ' follow as the next command? */
1277 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1278 NORM_IGNORECASE | SORT_STRINGSORT,
1279 (*cmdList)->command, 5, ifElse, -1) == 2) {
1281 /* Swap between if and else processing */
1282 processThese = !processThese;
1284 /* Process the ELSE part */
1286 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1288 /* Skip leading whitespace between condition and the command */
1289 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1291 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1294 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1296 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1305 /**************************************************************************
1308 * Simple on-line help. Help text is stored in the resource file.
1311 void WCMD_give_help (WCHAR *command) {
1315 command = WCMD_strtrim_leading_spaces(command);
1316 if (strlenW(command) == 0) {
1317 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1320 for (i=0; i<=WCMD_EXIT; i++) {
1321 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1322 command, -1, inbuilt[i], -1) == 2) {
1323 WCMD_output_asis (WCMD_LoadMessage(i));
1327 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1332 /****************************************************************************
1335 * Batch file jump instruction. Not the most efficient algorithm ;-)
1336 * Prints error message if the specified label cannot be found - the file pointer is
1337 * then at EOF, effectively stopping the batch file.
1338 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1341 void WCMD_goto (CMD_LIST **cmdList) {
1343 WCHAR string[MAX_PATH];
1344 WCHAR current[MAX_PATH];
1346 /* Do not process any more parts of a processed multipart or multilines command */
1347 if (cmdList) *cmdList = NULL;
1349 if (param1[0] == 0x00) {
1350 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1353 if (context != NULL) {
1354 WCHAR *paramStart = param1, *str;
1355 static const WCHAR eofW[] = {':','e','o','f','\0'};
1357 /* Handle special :EOF label */
1358 if (lstrcmpiW (eofW, param1) == 0) {
1359 context -> skip_rest = TRUE;
1363 /* Support goto :label as well as goto label */
1364 if (*paramStart == ':') paramStart++;
1366 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1367 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1369 while (isspaceW (*str)) str++;
1373 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1376 /* ignore space at the end */
1378 if (lstrcmpiW (current, paramStart) == 0) return;
1381 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1386 /*****************************************************************************
1389 * Push a directory onto the stack
1392 void WCMD_pushd (WCHAR *command) {
1393 struct env_stack *curdir;
1395 static const WCHAR parmD[] = {'/','D','\0'};
1397 if (strchrW(command, '/') != NULL) {
1398 SetLastError(ERROR_INVALID_PARAMETER);
1403 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1404 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1405 if( !curdir || !thisdir ) {
1408 WINE_ERR ("out of memory\n");
1412 /* Change directory using CD code with /D parameter */
1413 strcpyW(quals, parmD);
1414 GetCurrentDirectoryW (1024, thisdir);
1416 WCMD_setshow_default(command);
1422 curdir -> next = pushd_directories;
1423 curdir -> strings = thisdir;
1424 if (pushd_directories == NULL) {
1425 curdir -> u.stackdepth = 1;
1427 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1429 pushd_directories = curdir;
1434 /*****************************************************************************
1437 * Pop a directory from the stack
1440 void WCMD_popd (void) {
1441 struct env_stack *temp = pushd_directories;
1443 if (!pushd_directories)
1446 /* pop the old environment from the stack, and make it the current dir */
1447 pushd_directories = temp->next;
1448 SetCurrentDirectoryW(temp->strings);
1449 LocalFree (temp->strings);
1453 /****************************************************************************
1456 * Batch file conditional.
1458 * On entry, cmdlist will point to command containing the IF, and optionally
1459 * the first command to execute (if brackets not found)
1460 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1461 * If ('s were found, execute all within that bracket
1462 * Command may optionally be followed by an ELSE - need to skip instructions
1463 * in the else using the same logic
1465 * FIXME: Much more syntax checking needed!
1468 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1470 int negate = 0, test = 0;
1471 WCHAR condition[MAX_PATH], *command, *s;
1472 static const WCHAR notW[] = {'n','o','t','\0'};
1473 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1474 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1475 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1476 static const WCHAR eqeqW[] = {'=','=','\0'};
1477 static const WCHAR parmI[] = {'/','I','\0'};
1479 if (!lstrcmpiW (param1, notW)) {
1481 strcpyW (condition, param2);
1484 strcpyW (condition, param1);
1486 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1488 if (!lstrcmpiW (condition, errlvlW)) {
1489 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1490 WCMD_parameter (p, 2+negate, &command);
1492 else if (!lstrcmpiW (condition, existW)) {
1493 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1496 WCMD_parameter (p, 2+negate, &command);
1498 else if (!lstrcmpiW (condition, defdW)) {
1499 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1502 WCMD_parameter (p, 2+negate, &command);
1504 else if ((s = strstrW (p, eqeqW))) {
1506 if (strstrW (quals, parmI) == NULL) {
1507 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1510 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1512 WCMD_parameter (s, 1, &command);
1515 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1519 /* Process rest of IF statement which is on the same line
1520 Note: This may process all or some of the cmdList (eg a GOTO) */
1521 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1524 /****************************************************************************
1527 * Move a file, directory tree or wildcarded set of files.
1530 void WCMD_move (void) {
1533 WIN32_FIND_DATAW fd;
1535 WCHAR input[MAX_PATH];
1536 WCHAR output[MAX_PATH];
1538 WCHAR dir[MAX_PATH];
1539 WCHAR fname[MAX_PATH];
1540 WCHAR ext[MAX_PATH];
1542 if (param1[0] == 0x00) {
1543 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1547 /* If no destination supplied, assume current directory */
1548 if (param2[0] == 0x00) {
1549 strcpyW(param2, dotW);
1552 /* If 2nd parm is directory, then use original filename */
1553 /* Convert partial path to full path */
1554 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1555 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1556 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1557 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1559 /* Split into components */
1560 WCMD_splitpath(input, drive, dir, fname, ext);
1562 hff = FindFirstFileW(input, &fd);
1563 while (hff != INVALID_HANDLE_VALUE) {
1564 WCHAR dest[MAX_PATH];
1565 WCHAR src[MAX_PATH];
1568 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1570 /* Build src & dest name */
1571 strcpyW(src, drive);
1574 /* See if dest is an existing directory */
1575 attribs = GetFileAttributesW(output);
1576 if (attribs != INVALID_FILE_ATTRIBUTES &&
1577 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1578 strcpyW(dest, output);
1579 strcatW(dest, slashW);
1580 strcatW(dest, fd.cFileName);
1582 strcpyW(dest, output);
1585 strcatW(src, fd.cFileName);
1587 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1588 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1590 /* Check if file is read only, otherwise move it */
1591 attribs = GetFileAttributesW(src);
1592 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1593 (attribs & FILE_ATTRIBUTE_READONLY)) {
1594 SetLastError(ERROR_ACCESS_DENIED);
1599 /* If destination exists, prompt unless /Y supplied */
1600 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1602 WCHAR copycmd[MAXSTRING];
1605 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1606 if (strstrW (quals, parmNoY))
1608 else if (strstrW (quals, parmY))
1611 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1612 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1613 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1614 && ! lstrcmpiW (copycmd, parmY));
1617 /* Prompt if overwriting */
1619 WCHAR question[MAXSTRING];
1622 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1624 /* Ask for confirmation */
1625 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1626 ok = WCMD_ask_confirm(question, FALSE, NULL);
1628 /* So delete the destination prior to the move */
1630 if (!DeleteFileW(dest)) {
1631 WCMD_print_error ();
1640 status = MoveFileW(src, dest);
1642 status = 1; /* Anything other than 0 to prevent error msg below */
1647 WCMD_print_error ();
1651 /* Step on to next match */
1652 if (FindNextFileW(hff, &fd) == 0) {
1654 hff = INVALID_HANDLE_VALUE;
1660 /****************************************************************************
1663 * Wait for keyboard input.
1666 void WCMD_pause (void) {
1671 WCMD_output (anykey);
1672 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1673 sizeof(string)/sizeof(WCHAR), &count, NULL);
1676 /****************************************************************************
1679 * Delete a directory.
1682 void WCMD_remove_dir (WCHAR *command) {
1685 int argsProcessed = 0;
1686 WCHAR *argN = command;
1687 static const WCHAR parmS[] = {'/','S','\0'};
1688 static const WCHAR parmQ[] = {'/','Q','\0'};
1690 /* Loop through all args */
1692 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1693 if (argN && argN[0] != '/') {
1694 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1695 wine_dbgstr_w(quals));
1698 /* If subdirectory search not supplied, just try to remove
1699 and report error if it fails (eg if it contains a file) */
1700 if (strstrW (quals, parmS) == NULL) {
1701 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1703 /* Otherwise use ShFileOp to recursively remove a directory */
1706 SHFILEOPSTRUCTW lpDir;
1709 if (strstrW (quals, parmQ) == NULL) {
1711 WCHAR question[MAXSTRING];
1712 static const WCHAR fmt[] = {'%','s',' ','\0'};
1714 /* Ask for confirmation */
1715 wsprintfW(question, fmt, thisArg);
1716 ok = WCMD_ask_confirm(question, TRUE, NULL);
1718 /* Abort if answer is 'N' */
1725 lpDir.pFrom = thisArg;
1726 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1727 lpDir.wFunc = FO_DELETE;
1728 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1733 /* Handle no valid args */
1734 if (argsProcessed == 0) {
1735 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1741 /****************************************************************************
1747 void WCMD_rename (void) {
1751 WIN32_FIND_DATAW fd;
1752 WCHAR input[MAX_PATH];
1753 WCHAR *dotDst = NULL;
1755 WCHAR dir[MAX_PATH];
1756 WCHAR fname[MAX_PATH];
1757 WCHAR ext[MAX_PATH];
1762 /* Must be at least two args */
1763 if (param1[0] == 0x00 || param2[0] == 0x00) {
1764 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1769 /* Destination cannot contain a drive letter or directory separator */
1770 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1771 SetLastError(ERROR_INVALID_PARAMETER);
1777 /* Convert partial path to full path */
1778 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1779 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1780 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1781 dotDst = strchrW(param2, '.');
1783 /* Split into components */
1784 WCMD_splitpath(input, drive, dir, fname, ext);
1786 hff = FindFirstFileW(input, &fd);
1787 while (hff != INVALID_HANDLE_VALUE) {
1788 WCHAR dest[MAX_PATH];
1789 WCHAR src[MAX_PATH];
1790 WCHAR *dotSrc = NULL;
1793 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1795 /* FIXME: If dest name or extension is *, replace with filename/ext
1796 part otherwise use supplied name. This supports:
1798 ren jim.* fred.* etc
1799 However, windows has a more complex algorithm supporting eg
1800 ?'s and *'s mid name */
1801 dotSrc = strchrW(fd.cFileName, '.');
1803 /* Build src & dest name */
1804 strcpyW(src, drive);
1807 dirLen = strlenW(src);
1808 strcatW(src, fd.cFileName);
1811 if (param2[0] == '*') {
1812 strcatW(dest, fd.cFileName);
1813 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1815 strcatW(dest, param2);
1816 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1819 /* Build Extension */
1820 if (dotDst && (*(dotDst+1)=='*')) {
1821 if (dotSrc) strcatW(dest, dotSrc);
1822 } else if (dotDst) {
1823 if (dotDst) strcatW(dest, dotDst);
1826 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1827 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1829 /* Check if file is read only, otherwise move it */
1830 attribs = GetFileAttributesW(src);
1831 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1832 (attribs & FILE_ATTRIBUTE_READONLY)) {
1833 SetLastError(ERROR_ACCESS_DENIED);
1836 status = MoveFileW(src, dest);
1840 WCMD_print_error ();
1844 /* Step on to next match */
1845 if (FindNextFileW(hff, &fd) == 0) {
1847 hff = INVALID_HANDLE_VALUE;
1853 /*****************************************************************************
1856 * Make a copy of the environment.
1858 static WCHAR *WCMD_dupenv( const WCHAR *env )
1868 len += (strlenW(&env[len]) + 1);
1870 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1873 WINE_ERR("out of memory\n");
1876 memcpy (env_copy, env, len*sizeof (WCHAR));
1882 /*****************************************************************************
1885 * setlocal pushes the environment onto a stack
1886 * Save the environment as unicode so we don't screw anything up.
1888 void WCMD_setlocal (const WCHAR *s) {
1890 struct env_stack *env_copy;
1891 WCHAR cwd[MAX_PATH];
1893 /* DISABLEEXTENSIONS ignored */
1895 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1898 WINE_ERR ("out of memory\n");
1902 env = GetEnvironmentStringsW ();
1904 env_copy->strings = WCMD_dupenv (env);
1905 if (env_copy->strings)
1907 env_copy->next = saved_environment;
1908 saved_environment = env_copy;
1910 /* Save the current drive letter */
1911 GetCurrentDirectoryW(MAX_PATH, cwd);
1912 env_copy->u.cwd = cwd[0];
1915 LocalFree (env_copy);
1917 FreeEnvironmentStringsW (env);
1921 /*****************************************************************************
1924 * endlocal pops the environment off a stack
1925 * Note: When searching for '=', search from WCHAR position 1, to handle
1926 * special internal environment variables =C:, =D: etc
1928 void WCMD_endlocal (void) {
1929 WCHAR *env, *old, *p;
1930 struct env_stack *temp;
1933 if (!saved_environment)
1936 /* pop the old environment from the stack */
1937 temp = saved_environment;
1938 saved_environment = temp->next;
1940 /* delete the current environment, totally */
1941 env = GetEnvironmentStringsW ();
1942 old = WCMD_dupenv (GetEnvironmentStringsW ());
1945 n = strlenW(&old[len]) + 1;
1946 p = strchrW(&old[len] + 1, '=');
1950 SetEnvironmentVariableW (&old[len], NULL);
1955 FreeEnvironmentStringsW (env);
1957 /* restore old environment */
1958 env = temp->strings;
1961 n = strlenW(&env[len]) + 1;
1962 p = strchrW(&env[len] + 1, '=');
1966 SetEnvironmentVariableW (&env[len], p);
1971 /* Restore current drive letter */
1972 if (IsCharAlphaW(temp->u.cwd)) {
1974 WCHAR cwd[MAX_PATH];
1975 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1977 wsprintfW(envvar, fmt, temp->u.cwd);
1978 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1979 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1980 SetCurrentDirectoryW(cwd);
1988 /*****************************************************************************
1989 * WCMD_setshow_attrib
1991 * Display and optionally sets DOS attributes on a file or directory
1995 void WCMD_setshow_attrib (void) {
1999 WIN32_FIND_DATAW fd;
2000 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
2001 WCHAR *name = param1;
2003 DWORD attrib_clear=0;
2005 if (param1[0] == '+' || param1[0] == '-') {
2007 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
2008 switch (param1[1]) {
2009 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
2010 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
2011 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
2012 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
2014 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2017 switch (param1[0]) {
2018 case '+': attrib_set = attrib; break;
2019 case '-': attrib_clear = attrib; break;
2024 if (strlenW(name) == 0) {
2025 static const WCHAR slashStarW[] = {'\\','*','\0'};
2027 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
2028 strcatW (name, slashStarW);
2031 hff = FindFirstFileW(name, &fd);
2032 if (hff == INVALID_HANDLE_VALUE) {
2033 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
2037 if (attrib_set || attrib_clear) {
2038 fd.dwFileAttributes &= ~attrib_clear;
2039 fd.dwFileAttributes |= attrib_set;
2040 if (!fd.dwFileAttributes)
2041 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
2042 SetFileAttributesW(name, fd.dwFileAttributes);
2044 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
2045 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
2048 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
2051 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
2054 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
2057 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
2060 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
2063 WCMD_output (fmt, flags, fd.cFileName);
2064 for (count=0; count < 8; count++) flags[count] = ' ';
2066 } while (FindNextFileW(hff, &fd) != 0);
2071 /*****************************************************************************
2072 * WCMD_setshow_default
2074 * Set/Show the current default directory
2077 void WCMD_setshow_default (WCHAR *command) {
2083 WIN32_FIND_DATAW fd;
2085 static const WCHAR parmD[] = {'/','D','\0'};
2087 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2089 /* Skip /D and trailing whitespace if on the front of the command line */
2090 if (CompareStringW(LOCALE_USER_DEFAULT,
2091 NORM_IGNORECASE | SORT_STRINGSORT,
2092 command, 2, parmD, -1) == 2) {
2094 while (*command && *command==' ') command++;
2097 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2098 if (strlenW(command) == 0) {
2099 strcatW (cwd, newline);
2103 /* Remove any double quotes, which may be in the
2104 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2107 if (*command != '"') *pos++ = *command;
2112 /* Search for appropriate directory */
2113 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2114 hff = FindFirstFileW(string, &fd);
2115 while (hff != INVALID_HANDLE_VALUE) {
2116 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2117 WCHAR fpath[MAX_PATH];
2119 WCHAR dir[MAX_PATH];
2120 WCHAR fname[MAX_PATH];
2121 WCHAR ext[MAX_PATH];
2122 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2124 /* Convert path into actual directory spec */
2125 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2126 WCMD_splitpath(fpath, drive, dir, fname, ext);
2129 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2132 hff = INVALID_HANDLE_VALUE;
2136 /* Step on to next match */
2137 if (FindNextFileW(hff, &fd) == 0) {
2139 hff = INVALID_HANDLE_VALUE;
2144 /* Change to that directory */
2145 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2147 status = SetCurrentDirectoryW(string);
2150 WCMD_print_error ();
2154 /* Save away the actual new directory, to store as current location */
2155 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2157 /* Restore old directory if drive letter would change, and
2158 CD x:\directory /D (or pushd c:\directory) not supplied */
2159 if ((strstrW(quals, parmD) == NULL) &&
2160 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2161 SetCurrentDirectoryW(cwd);
2165 /* Set special =C: type environment variable, for drive letter of
2166 change of directory, even if path was restored due to missing
2167 /D (allows changing drive letter when not resident on that
2169 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2171 strcpyW(env, equalW);
2172 memcpy(env+1, string, 2 * sizeof(WCHAR));
2174 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2175 SetEnvironmentVariableW(env, string);
2182 /****************************************************************************
2185 * Set/Show the system date
2186 * FIXME: Can't change date yet
2189 void WCMD_setshow_date (void) {
2191 WCHAR curdate[64], buffer[64];
2193 static const WCHAR parmT[] = {'/','T','\0'};
2195 if (strlenW(param1) == 0) {
2196 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2197 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2198 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2199 if (strstrW (quals, parmT) == NULL) {
2200 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2201 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2202 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2204 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2208 else WCMD_print_error ();
2211 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2215 /****************************************************************************
2218 static int WCMD_compare( const void *a, const void *b )
2221 const WCHAR * const *str_a = a, * const *str_b = b;
2222 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2223 *str_a, -1, *str_b, -1 );
2224 if( r == CSTR_LESS_THAN ) return -1;
2225 if( r == CSTR_GREATER_THAN ) return 1;
2229 /****************************************************************************
2230 * WCMD_setshow_sortenv
2232 * sort variables into order for display
2233 * Optionally only display those who start with a stub
2234 * returns the count displayed
2236 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2238 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2241 if (stub) stublen = strlenW(stub);
2243 /* count the number of strings, and the total length */
2245 len += (strlenW(&s[len]) + 1);
2249 /* add the strings to an array */
2250 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2254 for( i=1; i<count; i++ )
2255 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2257 /* sort the array */
2258 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2261 for( i=0; i<count; i++ ) {
2262 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2263 NORM_IGNORECASE | SORT_STRINGSORT,
2264 str[i], stublen, stub, -1) == 2) {
2265 /* Don't display special internal variables */
2266 if (str[i][0] != '=') {
2267 WCMD_output_asis(str[i]);
2268 WCMD_output_asis(newline);
2275 return displayedcount;
2278 /****************************************************************************
2281 * Set/Show the environment variables
2284 void WCMD_setshow_env (WCHAR *s) {
2289 static const WCHAR parmP[] = {'/','P','\0'};
2291 if (param1[0] == 0x00 && quals[0] == 0x00) {
2292 env = GetEnvironmentStringsW();
2293 WCMD_setshow_sortenv( env, NULL );
2297 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2298 if (CompareStringW(LOCALE_USER_DEFAULT,
2299 NORM_IGNORECASE | SORT_STRINGSORT,
2300 s, 2, parmP, -1) == 2) {
2301 WCHAR string[MAXSTRING];
2305 while (*s && *s==' ') s++;
2307 WCMD_opt_s_strip_quotes(s);
2309 /* If no parameter, or no '=' sign, return an error */
2310 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2311 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2315 /* Output the prompt */
2317 if (strlenW(p) != 0) WCMD_output(p);
2319 /* Read the reply */
2320 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2321 sizeof(string)/sizeof(WCHAR), &count, NULL);
2323 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2324 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2325 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2326 wine_dbgstr_w(string));
2327 status = SetEnvironmentVariableW(s, string);
2334 WCMD_opt_s_strip_quotes(s);
2335 p = strchrW (s, '=');
2337 env = GetEnvironmentStringsW();
2338 if (WCMD_setshow_sortenv( env, s ) == 0) {
2339 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2346 if (strlenW(p) == 0) p = NULL;
2347 status = SetEnvironmentVariableW(s, p);
2348 gle = GetLastError();
2349 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2351 } else if ((!status)) WCMD_print_error();
2355 /****************************************************************************
2358 * Set/Show the path environment variable
2361 void WCMD_setshow_path (WCHAR *command) {
2365 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2366 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2368 if (strlenW(param1) == 0) {
2369 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2371 WCMD_output_asis ( pathEqW);
2372 WCMD_output_asis ( string);
2373 WCMD_output_asis ( newline);
2376 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2380 if (*command == '=') command++; /* Skip leading '=' */
2381 status = SetEnvironmentVariableW(pathW, command);
2382 if (!status) WCMD_print_error();
2386 /****************************************************************************
2387 * WCMD_setshow_prompt
2389 * Set or show the command prompt.
2392 void WCMD_setshow_prompt (void) {
2395 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2397 if (strlenW(param1) == 0) {
2398 SetEnvironmentVariableW(promptW, NULL);
2402 while ((*s == '=') || (*s == ' ')) s++;
2403 if (strlenW(s) == 0) {
2404 SetEnvironmentVariableW(promptW, NULL);
2406 else SetEnvironmentVariableW(promptW, s);
2410 /****************************************************************************
2413 * Set/Show the system time
2414 * FIXME: Can't change time yet
2417 void WCMD_setshow_time (void) {
2419 WCHAR curtime[64], buffer[64];
2422 static const WCHAR parmT[] = {'/','T','\0'};
2424 if (strlenW(param1) == 0) {
2426 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2427 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2428 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2429 if (strstrW (quals, parmT) == NULL) {
2430 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2431 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2432 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2434 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2438 else WCMD_print_error ();
2441 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2445 /****************************************************************************
2448 * Shift batch parameters.
2449 * Optional /n says where to start shifting (n=0-8)
2452 void WCMD_shift (WCHAR *command) {
2455 if (context != NULL) {
2456 WCHAR *pos = strchrW(command, '/');
2461 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2462 start = (*(pos+1) - '0');
2464 SetLastError(ERROR_INVALID_PARAMETER);
2469 WINE_TRACE("Shifting variables, starting at %d\n", start);
2470 for (i=start;i<=8;i++) {
2471 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2473 context -> shift_count[9] = context -> shift_count[9] + 1;
2478 /****************************************************************************
2481 * Set the console title
2483 void WCMD_title (WCHAR *command) {
2484 SetConsoleTitleW(command);
2487 /****************************************************************************
2490 * Copy a file to standard output.
2493 void WCMD_type (WCHAR *command) {
2496 WCHAR *argN = command;
2497 BOOL writeHeaders = FALSE;
2499 if (param1[0] == 0x00) {
2500 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2504 if (param2[0] != 0x00) writeHeaders = TRUE;
2506 /* Loop through all args */
2509 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2517 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2518 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2519 FILE_ATTRIBUTE_NORMAL, NULL);
2520 if (h == INVALID_HANDLE_VALUE) {
2521 WCMD_print_error ();
2522 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2526 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2527 WCMD_output(fmt, thisArg);
2529 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2530 if (count == 0) break; /* ReadFile reports success on EOF! */
2532 WCMD_output_asis (buffer);
2536 WCMD_output_asis (newline);
2541 /****************************************************************************
2544 * Output either a file or stdin to screen in pages
2547 void WCMD_more (WCHAR *command) {
2550 WCHAR *argN = command;
2552 WCHAR moreStrPage[100];
2555 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2556 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2557 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2558 ')',' ','-','-','\n','\0'};
2559 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2561 /* Prefix the NLS more with '-- ', then load the text */
2563 strcpyW(moreStr, moreStart);
2564 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2565 (sizeof(moreStr)/sizeof(WCHAR))-3);
2567 if (param1[0] == 0x00) {
2569 /* Wine implements pipes via temporary files, and hence stdin is
2570 effectively reading from the file. This means the prompts for
2571 more are satisfied by the next line from the input (file). To
2572 avoid this, ensure stdin is to the console */
2573 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2574 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2575 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2576 FILE_ATTRIBUTE_NORMAL, 0);
2577 WINE_TRACE("No parms - working probably in pipe mode\n");
2578 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2580 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2581 once you get in this bit unless due to a pipe, its going to end badly... */
2582 wsprintfW(moreStrPage, moreFmt, moreStr);
2584 WCMD_enter_paged_mode(moreStrPage);
2585 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2586 if (count == 0) break; /* ReadFile reports success on EOF! */
2588 WCMD_output_asis (buffer);
2590 WCMD_leave_paged_mode();
2592 /* Restore stdin to what it was */
2593 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2594 CloseHandle(hConIn);
2598 BOOL needsPause = FALSE;
2600 /* Loop through all args */
2601 WINE_TRACE("Parms supplied - working through each file\n");
2602 WCMD_enter_paged_mode(moreStrPage);
2605 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2613 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2614 WCMD_leave_paged_mode();
2615 WCMD_output_asis(moreStrPage);
2616 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2617 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2618 WCMD_enter_paged_mode(moreStrPage);
2622 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2623 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2624 FILE_ATTRIBUTE_NORMAL, NULL);
2625 if (h == INVALID_HANDLE_VALUE) {
2626 WCMD_print_error ();
2627 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2631 ULONG64 fileLen = 0;
2632 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2634 /* Get the file size */
2635 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2636 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2639 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2640 if (count == 0) break; /* ReadFile reports success on EOF! */
2644 /* Update % count (would be used in WCMD_output_asis as prompt) */
2645 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2647 WCMD_output_asis (buffer);
2653 WCMD_leave_paged_mode();
2657 /****************************************************************************
2660 * Display verify flag.
2661 * FIXME: We don't actually do anything with the verify flag other than toggle
2665 void WCMD_verify (WCHAR *command) {
2669 count = strlenW(command);
2671 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2672 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2675 if (lstrcmpiW(command, onW) == 0) {
2679 else if (lstrcmpiW(command, offW) == 0) {
2683 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2686 /****************************************************************************
2689 * Display version info.
2692 void WCMD_version (void) {
2694 WCMD_output (version_string);
2698 /****************************************************************************
2701 * Display volume info and/or set volume label. Returns 0 if error.
2704 int WCMD_volume (int mode, WCHAR *path) {
2706 DWORD count, serial;
2707 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2710 if (strlenW(path) == 0) {
2711 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2713 WCMD_print_error ();
2716 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2717 &serial, NULL, NULL, NULL, 0);
2720 static const WCHAR fmt[] = {'%','s','\\','\0'};
2721 if ((path[1] != ':') || (strlenW(path) != 2)) {
2722 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2725 wsprintfW (curdir, fmt, path);
2726 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2731 WCMD_print_error ();
2734 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2735 curdir[0], label, HIWORD(serial), LOWORD(serial));
2737 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2738 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2739 sizeof(string)/sizeof(WCHAR), &count, NULL);
2741 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2742 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2744 if (strlenW(path) != 0) {
2745 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2748 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2754 /**************************************************************************
2757 * Exit either the process, or just this batch program
2761 void WCMD_exit (CMD_LIST **cmdList) {
2763 static const WCHAR parmB[] = {'/','B','\0'};
2764 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2766 if (context && lstrcmpiW(quals, parmB) == 0) {
2768 context -> skip_rest = TRUE;
2776 /*****************************************************************************
2779 * Lists or sets file associations (assoc = TRUE)
2780 * Lists or sets file types (assoc = FALSE)
2782 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2785 DWORD accessOptions = KEY_READ;
2787 LONG rc = ERROR_SUCCESS;
2788 WCHAR keyValue[MAXSTRING];
2789 DWORD valueLen = MAXSTRING;
2791 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2792 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2794 /* See if parameter includes '=' */
2796 newValue = strchrW(command, '=');
2797 if (newValue) accessOptions |= KEY_WRITE;
2799 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2800 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2801 accessOptions, &key) != ERROR_SUCCESS) {
2802 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2806 /* If no parameters then list all associations */
2807 if (*command == 0x00) {
2810 /* Enumerate all the keys */
2811 while (rc != ERROR_NO_MORE_ITEMS) {
2812 WCHAR keyName[MAXSTRING];
2815 /* Find the next value */
2816 nameLen = MAXSTRING;
2817 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2819 if (rc == ERROR_SUCCESS) {
2821 /* Only interested in extension ones if assoc, or others
2823 if ((keyName[0] == '.' && assoc) ||
2824 (!(keyName[0] == '.') && (!assoc)))
2826 WCHAR subkey[MAXSTRING];
2827 strcpyW(subkey, keyName);
2828 if (!assoc) strcatW(subkey, shOpCmdW);
2830 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2832 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2833 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2834 WCMD_output_asis(keyName);
2835 WCMD_output_asis(equalW);
2836 /* If no default value found, leave line empty after '=' */
2837 if (rc == ERROR_SUCCESS) {
2838 WCMD_output_asis(keyValue);
2840 WCMD_output_asis(newline);
2841 RegCloseKey(readKey);
2849 /* Parameter supplied - if no '=' on command line, its a query */
2850 if (newValue == NULL) {
2852 WCHAR subkey[MAXSTRING];
2854 /* Query terminates the parameter at the first space */
2855 strcpyW(keyValue, command);
2856 space = strchrW(keyValue, ' ');
2857 if (space) *space=0x00;
2859 /* Set up key name */
2860 strcpyW(subkey, keyValue);
2861 if (!assoc) strcatW(subkey, shOpCmdW);
2863 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2865 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2866 WCMD_output_asis(command);
2867 WCMD_output_asis(equalW);
2868 /* If no default value found, leave line empty after '=' */
2869 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2870 WCMD_output_asis(newline);
2871 RegCloseKey(readKey);
2874 WCHAR msgbuffer[MAXSTRING];
2875 WCHAR outbuffer[MAXSTRING];
2877 /* Load the translated 'File association not found' */
2879 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2881 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2883 wsprintfW(outbuffer, msgbuffer, keyValue);
2884 WCMD_output_asis(outbuffer);
2888 /* Not a query - its a set or clear of a value */
2891 WCHAR subkey[MAXSTRING];
2893 /* Get pointer to new value */
2897 /* Set up key name */
2898 strcpyW(subkey, command);
2899 if (!assoc) strcatW(subkey, shOpCmdW);
2901 /* If nothing after '=' then clear value - only valid for ASSOC */
2902 if (*newValue == 0x00) {
2904 if (assoc) rc = RegDeleteKeyW(key, command);
2905 if (assoc && rc == ERROR_SUCCESS) {
2906 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2908 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2913 WCHAR msgbuffer[MAXSTRING];
2914 WCHAR outbuffer[MAXSTRING];
2916 /* Load the translated 'File association not found' */
2918 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2919 sizeof(msgbuffer)/sizeof(WCHAR));
2921 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2922 sizeof(msgbuffer)/sizeof(WCHAR));
2924 wsprintfW(outbuffer, msgbuffer, keyValue);
2925 WCMD_output_asis(outbuffer);
2929 /* It really is a set value = contents */
2931 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2932 accessOptions, NULL, &readKey, NULL);
2933 if (rc == ERROR_SUCCESS) {
2934 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2935 (LPBYTE)newValue, strlenW(newValue));
2936 RegCloseKey(readKey);
2939 if (rc != ERROR_SUCCESS) {
2943 WCMD_output_asis(command);
2944 WCMD_output_asis(equalW);
2945 WCMD_output_asis(newValue);
2946 WCMD_output_asis(newline);
2956 /****************************************************************************
2959 * Clear the terminal screen.
2962 void WCMD_color (void) {
2964 /* Emulate by filling the screen from the top left to bottom right with
2965 spaces, then moving the cursor to the top left afterwards */
2966 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2967 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2969 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2970 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2974 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2980 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2985 /* Convert the color hex digits */
2986 if (param1[0] == 0x00) {
2987 color = defaultColor;
2989 color = strtoulW(param1, NULL, 16);
2992 /* Fail if fg == bg color */
2993 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2998 /* Set the current screen contents and ensure all future writes
2999 remain this color */
3000 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3001 SetConsoleTextAttribute(hStdOut, color);