2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * On entry to each function, global variables quals, param1, param2 contain
25 * the qualifiers (uppercased and concatenated) and parameters entered, with
26 * environment-variable and batch parameter substitution already done.
31 * - No support for pipes, shell parameters
32 * - Lots of functionality missing from builtins
33 * - Messages etc need international support
36 #define WIN32_LEAN_AND_MEAN
40 #include "wine/debug.h"
42 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
44 static void WCMD_part_execute(CMD_LIST **commands, const WCHAR *firstcmd,
45 const WCHAR *variable, const WCHAR *value,
46 BOOL isIF, BOOL conditionTRUE);
48 struct env_stack *saved_environment;
49 struct env_stack *pushd_directories;
51 extern HINSTANCE hinst;
52 extern WCHAR inbuilt[][10];
53 extern int echo_mode, verify_mode, defaultColor;
54 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
55 extern BATCH_CONTEXT *context;
56 extern DWORD errorlevel;
58 static const WCHAR dotW[] = {'.','\0'};
59 static const WCHAR dotdotW[] = {'.','.','\0'};
60 static const WCHAR slashW[] = {'\\','\0'};
61 static const WCHAR starW[] = {'*','\0'};
62 static const WCHAR equalW[] = {'=','\0'};
63 static const WCHAR fslashW[] = {'/','\0'};
64 static const WCHAR onW[] = {'O','N','\0'};
65 static const WCHAR offW[] = {'O','F','F','\0'};
66 static const WCHAR parmY[] = {'/','Y','\0'};
67 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
68 static const WCHAR nullW[] = {'\0'};
70 /**************************************************************************
73 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
76 * Returns True if Y (or A) answer is selected
77 * If optionAll contains a pointer, ALL is allowed, and if answered
81 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
82 const BOOL *optionAll) {
84 WCHAR msgbuffer[MAXSTRING];
85 WCHAR Ybuffer[MAXSTRING];
86 WCHAR Nbuffer[MAXSTRING];
87 WCHAR Abuffer[MAXSTRING];
88 WCHAR answer[MAX_PATH] = {'\0'};
91 /* Load the translated 'Are you sure', plus valid answers */
92 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
93 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
94 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
95 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
97 /* Loop waiting on a Y or N */
98 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
99 static const WCHAR startBkt[] = {' ','(','\0'};
100 static const WCHAR endBkt[] = {')','?','\0'};
102 WCMD_output_asis (message);
104 WCMD_output_asis (msgbuffer);
106 WCMD_output_asis (startBkt);
107 WCMD_output_asis (Ybuffer);
108 WCMD_output_asis (fslashW);
109 WCMD_output_asis (Nbuffer);
111 WCMD_output_asis (fslashW);
112 WCMD_output_asis (Abuffer);
114 WCMD_output_asis (endBkt);
115 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
116 sizeof(answer)/sizeof(WCHAR), &count, NULL);
117 answer[0] = toupperW(answer[0]);
120 /* Return the answer */
121 return ((answer[0] == Ybuffer[0]) ||
122 (optionAll && (answer[0] == Abuffer[0])));
125 /****************************************************************************
128 * Clear the terminal screen.
131 void WCMD_clear_screen (void) {
133 /* Emulate by filling the screen from the top left to bottom right with
134 spaces, then moving the cursor to the top left afterwards */
135 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
136 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
138 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
143 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
147 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
148 SetConsoleCursorPosition(hStdOut, topLeft);
152 /****************************************************************************
155 * Change the default i/o device (ie redirect STDin/STDout).
158 void WCMD_change_tty (void) {
160 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
164 /****************************************************************************
169 void WCMD_choice (const WCHAR * command) {
171 static const WCHAR bellW[] = {7,0};
172 static const WCHAR commaW[] = {',',0};
173 static const WCHAR bracket_open[] = {'[',0};
174 static const WCHAR bracket_close[] = {']','?',0};
179 WCHAR *my_command = NULL;
180 WCHAR opt_default = 0;
181 DWORD opt_timeout = 0;
188 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
191 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
195 ptr = WCMD_skip_leading_spaces(my_command);
196 while (*ptr == '/') {
197 switch (toupperW(ptr[1])) {
200 /* the colon is optional */
204 if (!*ptr || isspaceW(*ptr)) {
205 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
206 HeapFree(GetProcessHeap(), 0, my_command);
210 /* remember the allowed keys (overwrite previous /C option) */
212 while (*ptr && (!isspaceW(*ptr)))
216 /* terminate allowed chars */
218 ptr = WCMD_skip_leading_spaces(&ptr[1]);
220 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
225 ptr = WCMD_skip_leading_spaces(&ptr[2]);
230 ptr = WCMD_skip_leading_spaces(&ptr[2]);
235 /* the colon is optional */
239 opt_default = *ptr++;
241 if (!opt_default || (*ptr != ',')) {
242 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
243 HeapFree(GetProcessHeap(), 0, my_command);
249 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
255 opt_timeout = atoiW(answer);
257 ptr = WCMD_skip_leading_spaces(ptr);
261 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
262 HeapFree(GetProcessHeap(), 0, my_command);
268 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
271 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
273 /* use default keys, when needed: localized versions of "Y"es and "No" */
275 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
276 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
281 /* print the question, when needed */
283 WCMD_output_asis(ptr);
287 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
291 /* print a list of all allowed answers inside brackets */
292 WCMD_output_asis(bracket_open);
295 while ((answer[0] = *ptr++)) {
296 WCMD_output_asis(answer);
298 WCMD_output_asis(commaW);
300 WCMD_output_asis(bracket_close);
305 /* FIXME: Add support for option /T */
306 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count, NULL);
309 answer[0] = toupperW(answer[0]);
311 ptr = strchrW(opt_c, answer[0]);
313 WCMD_output_asis(answer);
314 WCMD_output(newline);
316 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
318 errorlevel = (ptr - opt_c) + 1;
319 WINE_TRACE("answer: %d\n", errorlevel);
320 HeapFree(GetProcessHeap(), 0, my_command);
325 /* key not allowed: play the bell */
326 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
327 WCMD_output_asis(bellW);
332 /****************************************************************************
335 * Copy a file or wildcarded set.
336 * FIXME: Add support for a+b+c type syntax
339 void WCMD_copy (void) {
344 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
346 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
347 BOOL copyToDir = FALSE;
348 WCHAR srcspec[MAX_PATH];
352 WCHAR fname[MAX_PATH];
355 if (param1[0] == 0x00) {
356 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
360 /* Convert source into full spec */
361 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
362 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
363 if (srcpath[strlenW(srcpath) - 1] == '\\')
364 srcpath[strlenW(srcpath) - 1] = '\0';
366 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
367 attribs = GetFileAttributesW(srcpath);
371 strcpyW(srcspec, srcpath);
373 /* If a directory, then add \* on the end when searching */
374 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
375 strcatW(srcpath, slashW);
376 strcatW(srcspec, slashW);
377 strcatW(srcspec, starW);
379 WCMD_splitpath(srcpath, drive, dir, fname, ext);
380 strcpyW(srcpath, drive);
381 strcatW(srcpath, dir);
384 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
386 /* If no destination supplied, assume current directory */
387 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
388 if (param2[0] == 0x00) {
389 strcpyW(param2, dotW);
392 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
393 if (outpath[strlenW(outpath) - 1] == '\\')
394 outpath[strlenW(outpath) - 1] = '\0';
395 attribs = GetFileAttributesW(outpath);
396 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
397 strcatW (outpath, slashW);
400 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
401 wine_dbgstr_w(outpath), copyToDir);
403 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
404 if (strstrW (quals, parmNoY))
406 else if (strstrW (quals, parmY))
409 /* By default, we will force the overwrite in batch mode and ask for
410 * confirmation in interactive mode. */
413 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
414 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
415 * default behavior. */
416 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
417 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
418 if (!lstrcmpiW (copycmd, parmY))
420 else if (!lstrcmpiW (copycmd, parmNoY))
425 /* Loop through all source files */
426 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
427 hff = FindFirstFileW(srcspec, &fd);
428 if (hff != INVALID_HANDLE_VALUE) {
430 WCHAR outname[MAX_PATH];
431 WCHAR srcname[MAX_PATH];
432 BOOL overwrite = force;
434 /* Destination is either supplied filename, or source name in
435 supplied destination directory */
436 strcpyW(outname, outpath);
437 if (copyToDir) strcatW(outname, fd.cFileName);
438 strcpyW(srcname, srcpath);
439 strcatW(srcname, fd.cFileName);
441 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
442 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
444 /* Skip . and .., and directories */
445 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
447 WINE_TRACE("Skipping directories\n");
450 /* Prompt before overwriting */
451 else if (!overwrite) {
452 attribs = GetFileAttributesW(outname);
453 if (attribs != INVALID_FILE_ATTRIBUTES) {
454 WCHAR buffer[MAXSTRING];
455 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
456 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
458 else overwrite = TRUE;
461 /* Do the copy as appropriate */
463 status = CopyFileW(srcname, outname, FALSE);
464 if (!status) WCMD_print_error ();
467 } while (FindNextFileW(hff, &fd) != 0);
470 status = ERROR_FILE_NOT_FOUND;
475 /****************************************************************************
478 * Create a directory (and, if needed, any intermediate directories).
480 * Modifies its argument by replacing slashes temporarily with nulls.
483 static BOOL create_full_path(WCHAR* path)
487 /* don't mess with drive letter portion of path, if any */
492 /* Strip trailing slashes. */
493 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
496 /* Step through path, creating intermediate directories as needed. */
497 /* First component includes drive letter, if any. */
501 /* Skip to end of component */
502 while (*p == '\\') p++;
503 while (*p && *p != '\\') p++;
505 /* path is now the original full path */
506 return CreateDirectoryW(path, NULL);
508 /* Truncate path, create intermediate directory, and restore path */
510 rv = CreateDirectoryW(path, NULL);
512 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
519 void WCMD_create_dir (WCHAR *command) {
521 WCHAR *argN = command;
523 if (param1[0] == 0x00) {
524 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
527 /* Loop through all args */
529 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN);
531 if (!create_full_path(thisArg)) {
538 /* Parse the /A options given by the user on the commandline
539 * into a bitmask of wanted attributes (*wantSet),
540 * and a bitmask of unwanted attributes (*wantClear).
542 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
543 static const WCHAR parmA[] = {'/','A','\0'};
546 /* both are strictly 'out' parameters */
550 /* For each /A argument */
551 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
555 /* Skip optional : */
558 /* For each of the attribute specifier chars to this /A option */
559 for (; *p != 0 && *p != '/'; p++) {
568 /* Convert the attribute specifier to a bit in one of the masks */
570 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
571 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
572 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
573 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
575 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
585 /* If filename part of parameter is * or *.*,
586 * and neither /Q nor /P options were given,
587 * prompt the user whether to proceed.
588 * Returns FALSE if user says no, TRUE otherwise.
589 * *pPrompted is set to TRUE if the user is prompted.
590 * (If /P supplied, del will prompt for individual files later.)
592 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
593 static const WCHAR parmP[] = {'/','P','\0'};
594 static const WCHAR parmQ[] = {'/','Q','\0'};
596 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
597 static const WCHAR anyExt[]= {'.','*','\0'};
600 WCHAR fname[MAX_PATH];
602 WCHAR fpath[MAX_PATH];
604 /* Convert path into actual directory spec */
605 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
606 WCMD_splitpath(fpath, drive, dir, fname, ext);
608 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
609 if ((strcmpW(fname, starW) == 0) &&
610 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
612 WCHAR question[MAXSTRING];
613 static const WCHAR fmt[] = {'%','s',' ','\0'};
615 /* Caller uses this to suppress "file not found" warning later */
618 /* Ask for confirmation */
619 wsprintfW(question, fmt, fpath);
620 return WCMD_ask_confirm(question, TRUE, NULL);
623 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
627 /* Helper function for WCMD_delete().
628 * Deletes a single file, directory, or wildcard.
629 * If /S was given, does it recursively.
630 * Returns TRUE if a file was deleted.
632 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
634 static const WCHAR parmP[] = {'/','P','\0'};
635 static const WCHAR parmS[] = {'/','S','\0'};
636 static const WCHAR parmF[] = {'/','F','\0'};
638 DWORD unwanted_attrs;
640 WCHAR argCopy[MAX_PATH];
643 WCHAR fpath[MAX_PATH];
645 BOOL handleParm = TRUE;
647 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
649 strcpyW(argCopy, thisArg);
650 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
651 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
653 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
654 /* Skip this arg if user declines to delete *.* */
658 /* First, try to delete in the current directory */
659 hff = FindFirstFileW(argCopy, &fd);
660 if (hff == INVALID_HANDLE_VALUE) {
666 /* Support del <dirname> by just deleting all files dirname\* */
668 && (strchrW(argCopy,'*') == NULL)
669 && (strchrW(argCopy,'?') == NULL)
670 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
672 WCHAR modifiedParm[MAX_PATH];
673 static const WCHAR slashStar[] = {'\\','*','\0'};
675 strcpyW(modifiedParm, argCopy);
676 strcatW(modifiedParm, slashStar);
679 WCMD_delete_one(modifiedParm);
681 } else if (handleParm) {
683 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
684 strcpyW (fpath, argCopy);
686 p = strrchrW (fpath, '\\');
689 strcatW (fpath, fd.cFileName);
691 else strcpyW (fpath, fd.cFileName);
692 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
695 /* Handle attribute matching (/A) */
696 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
697 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
699 /* /P means prompt for each file */
700 if (ok && strstrW (quals, parmP) != NULL) {
701 WCHAR question[MAXSTRING];
703 /* Ask for confirmation */
704 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
705 ok = WCMD_ask_confirm(question, FALSE, NULL);
708 /* Only proceed if ok to */
711 /* If file is read only, and /A:r or /F supplied, delete it */
712 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
713 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
714 strstrW (quals, parmF) != NULL)) {
715 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
718 /* Now do the delete */
719 if (!DeleteFileW(fpath)) WCMD_print_error ();
723 } while (FindNextFileW(hff, &fd) != 0);
727 /* Now recurse into all subdirectories handling the parameter in the same way */
728 if (strstrW (quals, parmS) != NULL) {
730 WCHAR thisDir[MAX_PATH];
735 WCHAR fname[MAX_PATH];
738 /* Convert path into actual directory spec */
739 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
740 WCMD_splitpath(thisDir, drive, dir, fname, ext);
742 strcpyW(thisDir, drive);
743 strcatW(thisDir, dir);
744 cPos = strlenW(thisDir);
746 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
748 /* Append '*' to the directory */
750 thisDir[cPos+1] = 0x00;
752 hff = FindFirstFileW(thisDir, &fd);
754 /* Remove residual '*' */
755 thisDir[cPos] = 0x00;
757 if (hff != INVALID_HANDLE_VALUE) {
758 DIRECTORY_STACK *allDirs = NULL;
759 DIRECTORY_STACK *lastEntry = NULL;
762 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
763 (strcmpW(fd.cFileName, dotdotW) != 0) &&
764 (strcmpW(fd.cFileName, dotW) != 0)) {
766 DIRECTORY_STACK *nextDir;
767 WCHAR subParm[MAX_PATH];
769 /* Work out search parameter in sub dir */
770 strcpyW (subParm, thisDir);
771 strcatW (subParm, fd.cFileName);
772 strcatW (subParm, slashW);
773 strcatW (subParm, fname);
774 strcatW (subParm, ext);
775 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
777 /* Allocate memory, add to list */
778 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
779 if (allDirs == NULL) allDirs = nextDir;
780 if (lastEntry != NULL) lastEntry->next = nextDir;
782 nextDir->next = NULL;
783 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
784 (strlenW(subParm)+1) * sizeof(WCHAR));
785 strcpyW(nextDir->dirName, subParm);
787 } while (FindNextFileW(hff, &fd) != 0);
790 /* Go through each subdir doing the delete */
791 while (allDirs != NULL) {
792 DIRECTORY_STACK *tempDir;
794 tempDir = allDirs->next;
795 found |= WCMD_delete_one (allDirs->dirName);
797 HeapFree(GetProcessHeap(),0,allDirs->dirName);
798 HeapFree(GetProcessHeap(),0,allDirs);
807 /****************************************************************************
810 * Delete a file or wildcarded set.
813 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
814 * - Each set is a pattern, eg /ahr /as-r means
815 * readonly+hidden OR nonreadonly system files
816 * - The '-' applies to a single field, ie /a:-hr means read only
820 BOOL WCMD_delete (WCHAR *command) {
823 BOOL argsProcessed = FALSE;
824 BOOL foundAny = FALSE;
828 for (argno=0; ; argno++) {
833 thisArg = WCMD_parameter (command, argno, &argN);
835 break; /* no more parameters */
837 continue; /* skip options */
839 argsProcessed = TRUE;
840 found = WCMD_delete_one(thisArg);
843 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
848 /* Handle no valid args */
850 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
855 /****************************************************************************
858 * Echo input to the screen (or not). We don't try to emulate the bugs
859 * in DOS (try typing "ECHO ON AGAIN" for an example).
862 void WCMD_echo (const WCHAR *command) {
865 const WCHAR *origcommand = command;
867 if (command[0]==' ' || command[0]=='\t' || command[0]=='.' || command[0]==':')
869 count = strlenW(command);
870 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':') {
871 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
872 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
875 if (lstrcmpiW(command, onW) == 0) {
879 if (lstrcmpiW(command, offW) == 0) {
883 WCMD_output_asis (command);
884 WCMD_output (newline);
888 /**************************************************************************
891 * Batch file loop processing.
893 * On entry: cmdList contains the syntax up to the set
894 * next cmdList and all in that bracket contain the set data
895 * next cmdlist contains the DO cmd
896 * following that is either brackets or && entries (as per if)
900 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
905 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
906 const WCHAR inTabW[] = {'i', 'n', '\t', '\0'};
907 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
908 const WCHAR doTabW[] = {'d', 'o', '\t', '\0'};
909 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
915 BOOL expandDirs = FALSE;
916 BOOL useNumbers = FALSE;
917 BOOL doFileset = FALSE;
918 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
920 CMD_LIST *thisCmdStart;
923 /* Handle optional qualifiers (multiple are allowed) */
924 while (*curPos && *curPos == '/') {
925 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
927 switch (toupperW(*curPos)) {
928 case 'D': curPos++; expandDirs = TRUE; break;
929 case 'L': curPos++; useNumbers = TRUE; break;
931 /* Recursive is special case - /R can have an optional path following it */
932 /* filenamesets are another special case - /F can have an optional options following it */
936 BOOL isRecursive = (*curPos == 'R');
941 /* Skip whitespace */
943 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
945 /* Next parm is either qualifier, path/options or variable -
946 only care about it if it is the path/options */
947 if (*curPos && *curPos != '/' && *curPos != '%') {
948 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
949 else WINE_FIXME("/F needs to handle options\n");
954 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
958 /* Skip whitespace between qualifiers */
959 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
962 /* Skip whitespace before variable */
963 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
965 /* Ensure line continues with variable */
966 if (!*curPos || *curPos != '%') {
967 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
971 /* Variable should follow */
973 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
974 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
976 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
979 /* Skip whitespace before IN */
980 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
982 /* Ensure line continues with IN */
984 || (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
985 curPos, 3, inW, -1) != CSTR_EQUAL
986 && CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
987 curPos, 3, inTabW, -1) != CSTR_EQUAL)) {
988 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
992 /* Save away where the set of data starts and the variable */
993 thisDepth = (*cmdList)->bracketDepth;
994 *cmdList = (*cmdList)->nextcommand;
995 setStart = (*cmdList);
997 /* Skip until the close bracket */
998 WINE_TRACE("Searching %p as the set\n", *cmdList);
1000 (*cmdList)->command != NULL &&
1001 (*cmdList)->bracketDepth > thisDepth) {
1002 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1003 *cmdList = (*cmdList)->nextcommand;
1006 /* Skip the close bracket, if there is one */
1007 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1009 /* Syntax error if missing close bracket, or nothing following it
1010 and once we have the complete set, we expect a DO */
1011 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
1012 if ((*cmdList == NULL)
1013 || (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1014 (*cmdList)->command, 3, doW, -1) != CSTR_EQUAL
1015 && CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1016 (*cmdList)->command, 3, doTabW, -1) != CSTR_EQUAL)) {
1017 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1021 /* Save away the starting position for the commands (and offset for the
1023 cmdStart = *cmdList;
1025 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1029 /* Loop through all set entries */
1031 thisSet->command != NULL &&
1032 thisSet->bracketDepth >= thisDepth) {
1034 /* Loop through all entries on the same line */
1038 WINE_TRACE("Processing for set %p\n", thisSet);
1040 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
1043 * If the parameter within the set has a wildcard then search for matching files
1044 * otherwise do a literal substitution.
1046 static const WCHAR wildcards[] = {'*','?','\0'};
1047 thisCmdStart = cmdStart;
1050 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1052 if (!useNumbers && !doFileset) {
1053 if (strpbrkW (item, wildcards)) {
1054 hff = FindFirstFileW(item, &fd);
1055 if (hff != INVALID_HANDLE_VALUE) {
1057 BOOL isDirectory = FALSE;
1059 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1061 /* Handle as files or dirs appropriately, but ignore . and .. */
1062 if (isDirectory == expandDirs &&
1063 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1064 (strcmpW(fd.cFileName, dotW) != 0))
1066 thisCmdStart = cmdStart;
1067 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1068 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1069 fd.cFileName, FALSE, TRUE);
1072 } while (FindNextFileW(hff, &fd) != 0);
1076 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1079 } else if (useNumbers) {
1080 /* Convert the first 3 numbers to signed longs and save */
1081 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1082 /* else ignore them! */
1084 /* Filesets - either a list of files, or a command to run and parse the output */
1085 } else if (doFileset && *itemStart != '"') {
1088 WCHAR temp_file[MAX_PATH];
1090 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1091 wine_dbgstr_w(item));
1093 /* If backquote or single quote, we need to launch that command
1094 and parse the results - use a temporary file */
1095 if (*itemStart == '`' || *itemStart == '\'') {
1097 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1098 static const WCHAR redirOut[] = {'>','%','s','\0'};
1099 static const WCHAR cmdW[] = {'C','M','D','\0'};
1101 /* Remove trailing character */
1102 itemStart[strlenW(itemStart)-1] = 0x00;
1104 /* Get temp filename */
1105 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1106 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1108 /* Execute program and redirect output */
1109 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1110 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1112 /* Open the file, read line by line and process */
1113 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1114 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1117 /* Open the file, read line by line and process */
1118 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1119 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1122 /* Process the input file */
1123 if (input == INVALID_HANDLE_VALUE) {
1124 WCMD_print_error ();
1125 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1127 return; /* FOR loop aborts at first failure here */
1131 WCHAR buffer[MAXSTRING] = {'\0'};
1132 WCHAR *where, *parm;
1134 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1136 /* Skip blank lines*/
1137 parm = WCMD_parameter (buffer, 0, &where);
1138 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1139 wine_dbgstr_w(buffer));
1142 /* FIXME: The following should be moved into its own routine and
1143 reused for the string literal parsing below */
1144 thisCmdStart = cmdStart;
1145 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1146 cmdEnd = thisCmdStart;
1152 CloseHandle (input);
1155 /* Delete the temporary file */
1156 if (*itemStart == '`' || *itemStart == '\'') {
1157 DeleteFileW(temp_file);
1160 /* Filesets - A string literal */
1161 } else if (doFileset && *itemStart == '"') {
1162 WCHAR buffer[MAXSTRING] = {'\0'};
1163 WCHAR *where, *parm;
1165 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1166 strcpyW(buffer, item);
1167 parm = WCMD_parameter (buffer, 0, &where);
1168 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1169 wine_dbgstr_w(buffer));
1172 /* FIXME: The following should be moved into its own routine and
1173 reused for the string literal parsing below */
1174 thisCmdStart = cmdStart;
1175 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1176 cmdEnd = thisCmdStart;
1180 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1181 cmdEnd = thisCmdStart;
1185 /* Move onto the next set line */
1186 thisSet = thisSet->nextcommand;
1189 /* If /L is provided, now run the for loop */
1192 static const WCHAR fmt[] = {'%','d','\0'};
1194 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1195 numbers[0], numbers[2], numbers[1]);
1197 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1200 sprintfW(thisNum, fmt, i);
1201 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1203 thisCmdStart = cmdStart;
1204 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1205 cmdEnd = thisCmdStart;
1209 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1210 all processing, OR it should be pointing to the end of && processing OR
1211 it should be pointing at the NULL end of bracket for the DO. The return
1212 value needs to be the NEXT command to execute, which it either is, or
1213 we need to step over the closing bracket */
1215 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1219 /*****************************************************************************
1222 * Execute a command, and any && or bracketed follow on to the command. The
1223 * first command to be executed may not be at the front of the
1224 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1226 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1227 const WCHAR *variable, const WCHAR *value,
1228 BOOL isIF, BOOL conditionTRUE) {
1230 CMD_LIST *curPosition = *cmdList;
1231 int myDepth = (*cmdList)->bracketDepth;
1233 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1234 cmdList, wine_dbgstr_w(firstcmd),
1235 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1238 /* Skip leading whitespace between condition and the command */
1239 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1241 /* Process the first command, if there is one */
1242 if (conditionTRUE && firstcmd && *firstcmd) {
1243 WCHAR *command = WCMD_strdupW(firstcmd);
1244 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1245 HeapFree(GetProcessHeap(), 0, command);
1249 /* If it didn't move the position, step to next command */
1250 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1252 /* Process any other parts of the command */
1254 BOOL processThese = TRUE;
1256 if (isIF) processThese = conditionTRUE;
1259 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1261 /* execute all appropriate commands */
1262 curPosition = *cmdList;
1264 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1266 (*cmdList)->prevDelim,
1267 (*cmdList)->bracketDepth, myDepth);
1269 /* Execute any statements appended to the line */
1270 /* FIXME: Only if previous call worked for && or failed for || */
1271 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1272 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1273 if (processThese && (*cmdList)->command) {
1274 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1277 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1279 /* Execute any appended to the statement with (...) */
1280 } else if ((*cmdList)->bracketDepth > myDepth) {
1282 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1283 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1285 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1287 /* End of the command - does 'ELSE ' follow as the next command? */
1289 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1290 NORM_IGNORECASE | SORT_STRINGSORT,
1291 (*cmdList)->command, 5, ifElse, -1) == CSTR_EQUAL) {
1293 /* Swap between if and else processing */
1294 processThese = !processThese;
1296 /* Process the ELSE part */
1298 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1300 /* Skip leading whitespace between condition and the command */
1301 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1303 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1306 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1308 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1317 /**************************************************************************
1320 * Simple on-line help. Help text is stored in the resource file.
1323 void WCMD_give_help (const WCHAR *command) {
1327 command = WCMD_skip_leading_spaces((WCHAR*) command);
1328 if (strlenW(command) == 0) {
1329 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1332 for (i=0; i<=WCMD_EXIT; i++) {
1333 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1334 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1335 WCMD_output_asis (WCMD_LoadMessage(i));
1339 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1344 /****************************************************************************
1347 * Batch file jump instruction. Not the most efficient algorithm ;-)
1348 * Prints error message if the specified label cannot be found - the file pointer is
1349 * then at EOF, effectively stopping the batch file.
1350 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1353 void WCMD_goto (CMD_LIST **cmdList) {
1355 WCHAR string[MAX_PATH];
1356 WCHAR current[MAX_PATH];
1358 /* Do not process any more parts of a processed multipart or multilines command */
1359 if (cmdList) *cmdList = NULL;
1361 if (param1[0] == 0x00) {
1362 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1365 if (context != NULL) {
1366 WCHAR *paramStart = param1, *str;
1367 static const WCHAR eofW[] = {':','e','o','f','\0'};
1369 /* Handle special :EOF label */
1370 if (lstrcmpiW (eofW, param1) == 0) {
1371 context -> skip_rest = TRUE;
1375 /* Support goto :label as well as goto label */
1376 if (*paramStart == ':') paramStart++;
1378 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1379 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1381 while (isspaceW (*str)) str++;
1385 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1388 /* ignore space at the end */
1390 if (lstrcmpiW (current, paramStart) == 0) return;
1393 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1398 /*****************************************************************************
1401 * Push a directory onto the stack
1404 void WCMD_pushd (WCHAR *command) {
1405 struct env_stack *curdir;
1407 static const WCHAR parmD[] = {'/','D','\0'};
1409 if (strchrW(command, '/') != NULL) {
1410 SetLastError(ERROR_INVALID_PARAMETER);
1415 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1416 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1417 if( !curdir || !thisdir ) {
1420 WINE_ERR ("out of memory\n");
1424 /* Change directory using CD code with /D parameter */
1425 strcpyW(quals, parmD);
1426 GetCurrentDirectoryW (1024, thisdir);
1428 WCMD_setshow_default(command);
1434 curdir -> next = pushd_directories;
1435 curdir -> strings = thisdir;
1436 if (pushd_directories == NULL) {
1437 curdir -> u.stackdepth = 1;
1439 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1441 pushd_directories = curdir;
1446 /*****************************************************************************
1449 * Pop a directory from the stack
1452 void WCMD_popd (void) {
1453 struct env_stack *temp = pushd_directories;
1455 if (!pushd_directories)
1458 /* pop the old environment from the stack, and make it the current dir */
1459 pushd_directories = temp->next;
1460 SetCurrentDirectoryW(temp->strings);
1461 LocalFree (temp->strings);
1465 /****************************************************************************
1468 * Batch file conditional.
1470 * On entry, cmdlist will point to command containing the IF, and optionally
1471 * the first command to execute (if brackets not found)
1472 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1473 * If ('s were found, execute all within that bracket
1474 * Command may optionally be followed by an ELSE - need to skip instructions
1475 * in the else using the same logic
1477 * FIXME: Much more syntax checking needed!
1480 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1482 int negate = 0, test = 0;
1483 WCHAR condition[MAX_PATH], *command, *s;
1484 static const WCHAR notW[] = {'n','o','t','\0'};
1485 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1486 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1487 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1488 static const WCHAR eqeqW[] = {'=','=','\0'};
1489 static const WCHAR parmI[] = {'/','I','\0'};
1491 if (!lstrcmpiW (param1, notW)) {
1493 strcpyW (condition, param2);
1496 strcpyW (condition, param1);
1498 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1500 if (!lstrcmpiW (condition, errlvlW)) {
1501 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1502 WCMD_parameter (p, 2+negate, &command);
1504 else if (!lstrcmpiW (condition, existW)) {
1505 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1508 WCMD_parameter (p, 2+negate, &command);
1510 else if (!lstrcmpiW (condition, defdW)) {
1511 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1514 WCMD_parameter (p, 2+negate, &command);
1516 else if ((s = strstrW (p, eqeqW))) {
1518 if (strstrW (quals, parmI) == NULL) {
1519 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1522 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1524 WCMD_parameter (s, 1, &command);
1527 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1531 /* Process rest of IF statement which is on the same line
1532 Note: This may process all or some of the cmdList (eg a GOTO) */
1533 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1536 /****************************************************************************
1539 * Move a file, directory tree or wildcarded set of files.
1542 void WCMD_move (void) {
1545 WIN32_FIND_DATAW fd;
1547 WCHAR input[MAX_PATH];
1548 WCHAR output[MAX_PATH];
1550 WCHAR dir[MAX_PATH];
1551 WCHAR fname[MAX_PATH];
1552 WCHAR ext[MAX_PATH];
1554 if (param1[0] == 0x00) {
1555 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1559 /* If no destination supplied, assume current directory */
1560 if (param2[0] == 0x00) {
1561 strcpyW(param2, dotW);
1564 /* If 2nd parm is directory, then use original filename */
1565 /* Convert partial path to full path */
1566 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1567 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1568 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1569 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1571 /* Split into components */
1572 WCMD_splitpath(input, drive, dir, fname, ext);
1574 hff = FindFirstFileW(input, &fd);
1575 while (hff != INVALID_HANDLE_VALUE) {
1576 WCHAR dest[MAX_PATH];
1577 WCHAR src[MAX_PATH];
1580 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1582 /* Build src & dest name */
1583 strcpyW(src, drive);
1586 /* See if dest is an existing directory */
1587 attribs = GetFileAttributesW(output);
1588 if (attribs != INVALID_FILE_ATTRIBUTES &&
1589 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1590 strcpyW(dest, output);
1591 strcatW(dest, slashW);
1592 strcatW(dest, fd.cFileName);
1594 strcpyW(dest, output);
1597 strcatW(src, fd.cFileName);
1599 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1600 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1602 /* Check if file is read only, otherwise move it */
1603 attribs = GetFileAttributesW(src);
1604 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1605 (attribs & FILE_ATTRIBUTE_READONLY)) {
1606 SetLastError(ERROR_ACCESS_DENIED);
1611 /* If destination exists, prompt unless /Y supplied */
1612 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1614 WCHAR copycmd[MAXSTRING];
1617 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1618 if (strstrW (quals, parmNoY))
1620 else if (strstrW (quals, parmY))
1623 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1624 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1625 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1626 && ! lstrcmpiW (copycmd, parmY));
1629 /* Prompt if overwriting */
1631 WCHAR question[MAXSTRING];
1634 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1636 /* Ask for confirmation */
1637 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1638 ok = WCMD_ask_confirm(question, FALSE, NULL);
1640 /* So delete the destination prior to the move */
1642 if (!DeleteFileW(dest)) {
1643 WCMD_print_error ();
1652 status = MoveFileW(src, dest);
1654 status = 1; /* Anything other than 0 to prevent error msg below */
1659 WCMD_print_error ();
1663 /* Step on to next match */
1664 if (FindNextFileW(hff, &fd) == 0) {
1666 hff = INVALID_HANDLE_VALUE;
1672 /****************************************************************************
1675 * Wait for keyboard input.
1678 void WCMD_pause (void) {
1683 WCMD_output (anykey);
1684 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1685 sizeof(string)/sizeof(WCHAR), &count, NULL);
1688 /****************************************************************************
1691 * Delete a directory.
1694 void WCMD_remove_dir (WCHAR *command) {
1697 int argsProcessed = 0;
1698 WCHAR *argN = command;
1699 static const WCHAR parmS[] = {'/','S','\0'};
1700 static const WCHAR parmQ[] = {'/','Q','\0'};
1702 /* Loop through all args */
1704 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1705 if (argN && argN[0] != '/') {
1706 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1707 wine_dbgstr_w(quals));
1710 /* If subdirectory search not supplied, just try to remove
1711 and report error if it fails (eg if it contains a file) */
1712 if (strstrW (quals, parmS) == NULL) {
1713 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1715 /* Otherwise use ShFileOp to recursively remove a directory */
1718 SHFILEOPSTRUCTW lpDir;
1721 if (strstrW (quals, parmQ) == NULL) {
1723 WCHAR question[MAXSTRING];
1724 static const WCHAR fmt[] = {'%','s',' ','\0'};
1726 /* Ask for confirmation */
1727 wsprintfW(question, fmt, thisArg);
1728 ok = WCMD_ask_confirm(question, TRUE, NULL);
1730 /* Abort if answer is 'N' */
1737 lpDir.pFrom = thisArg;
1738 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1739 lpDir.wFunc = FO_DELETE;
1740 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1745 /* Handle no valid args */
1746 if (argsProcessed == 0) {
1747 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1753 /****************************************************************************
1759 void WCMD_rename (void) {
1763 WIN32_FIND_DATAW fd;
1764 WCHAR input[MAX_PATH];
1765 WCHAR *dotDst = NULL;
1767 WCHAR dir[MAX_PATH];
1768 WCHAR fname[MAX_PATH];
1769 WCHAR ext[MAX_PATH];
1774 /* Must be at least two args */
1775 if (param1[0] == 0x00 || param2[0] == 0x00) {
1776 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1781 /* Destination cannot contain a drive letter or directory separator */
1782 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1783 SetLastError(ERROR_INVALID_PARAMETER);
1789 /* Convert partial path to full path */
1790 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1791 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1792 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1793 dotDst = strchrW(param2, '.');
1795 /* Split into components */
1796 WCMD_splitpath(input, drive, dir, fname, ext);
1798 hff = FindFirstFileW(input, &fd);
1799 while (hff != INVALID_HANDLE_VALUE) {
1800 WCHAR dest[MAX_PATH];
1801 WCHAR src[MAX_PATH];
1802 WCHAR *dotSrc = NULL;
1805 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1807 /* FIXME: If dest name or extension is *, replace with filename/ext
1808 part otherwise use supplied name. This supports:
1810 ren jim.* fred.* etc
1811 However, windows has a more complex algorithm supporting eg
1812 ?'s and *'s mid name */
1813 dotSrc = strchrW(fd.cFileName, '.');
1815 /* Build src & dest name */
1816 strcpyW(src, drive);
1819 dirLen = strlenW(src);
1820 strcatW(src, fd.cFileName);
1823 if (param2[0] == '*') {
1824 strcatW(dest, fd.cFileName);
1825 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1827 strcatW(dest, param2);
1828 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1831 /* Build Extension */
1832 if (dotDst && (*(dotDst+1)=='*')) {
1833 if (dotSrc) strcatW(dest, dotSrc);
1834 } else if (dotDst) {
1835 if (dotDst) strcatW(dest, dotDst);
1838 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1839 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1841 /* Check if file is read only, otherwise move it */
1842 attribs = GetFileAttributesW(src);
1843 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1844 (attribs & FILE_ATTRIBUTE_READONLY)) {
1845 SetLastError(ERROR_ACCESS_DENIED);
1848 status = MoveFileW(src, dest);
1852 WCMD_print_error ();
1856 /* Step on to next match */
1857 if (FindNextFileW(hff, &fd) == 0) {
1859 hff = INVALID_HANDLE_VALUE;
1865 /*****************************************************************************
1868 * Make a copy of the environment.
1870 static WCHAR *WCMD_dupenv( const WCHAR *env )
1880 len += (strlenW(&env[len]) + 1);
1882 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1885 WINE_ERR("out of memory\n");
1888 memcpy (env_copy, env, len*sizeof (WCHAR));
1894 /*****************************************************************************
1897 * setlocal pushes the environment onto a stack
1898 * Save the environment as unicode so we don't screw anything up.
1900 void WCMD_setlocal (const WCHAR *s) {
1902 struct env_stack *env_copy;
1903 WCHAR cwd[MAX_PATH];
1905 /* DISABLEEXTENSIONS ignored */
1907 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1910 WINE_ERR ("out of memory\n");
1914 env = GetEnvironmentStringsW ();
1916 env_copy->strings = WCMD_dupenv (env);
1917 if (env_copy->strings)
1919 env_copy->next = saved_environment;
1920 saved_environment = env_copy;
1922 /* Save the current drive letter */
1923 GetCurrentDirectoryW(MAX_PATH, cwd);
1924 env_copy->u.cwd = cwd[0];
1927 LocalFree (env_copy);
1929 FreeEnvironmentStringsW (env);
1933 /*****************************************************************************
1936 * endlocal pops the environment off a stack
1937 * Note: When searching for '=', search from WCHAR position 1, to handle
1938 * special internal environment variables =C:, =D: etc
1940 void WCMD_endlocal (void) {
1941 WCHAR *env, *old, *p;
1942 struct env_stack *temp;
1945 if (!saved_environment)
1948 /* pop the old environment from the stack */
1949 temp = saved_environment;
1950 saved_environment = temp->next;
1952 /* delete the current environment, totally */
1953 env = GetEnvironmentStringsW ();
1954 old = WCMD_dupenv (GetEnvironmentStringsW ());
1957 n = strlenW(&old[len]) + 1;
1958 p = strchrW(&old[len] + 1, '=');
1962 SetEnvironmentVariableW (&old[len], NULL);
1967 FreeEnvironmentStringsW (env);
1969 /* restore old environment */
1970 env = temp->strings;
1973 n = strlenW(&env[len]) + 1;
1974 p = strchrW(&env[len] + 1, '=');
1978 SetEnvironmentVariableW (&env[len], p);
1983 /* Restore current drive letter */
1984 if (IsCharAlphaW(temp->u.cwd)) {
1986 WCHAR cwd[MAX_PATH];
1987 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1989 wsprintfW(envvar, fmt, temp->u.cwd);
1990 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1991 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1992 SetCurrentDirectoryW(cwd);
2000 /*****************************************************************************
2001 * WCMD_setshow_attrib
2003 * Display and optionally sets DOS attributes on a file or directory
2007 void WCMD_setshow_attrib (void) {
2011 WIN32_FIND_DATAW fd;
2012 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
2013 WCHAR *name = param1;
2015 DWORD attrib_clear=0;
2017 if (param1[0] == '+' || param1[0] == '-') {
2019 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
2020 switch (param1[1]) {
2021 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
2022 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
2023 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
2024 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
2026 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2029 switch (param1[0]) {
2030 case '+': attrib_set = attrib; break;
2031 case '-': attrib_clear = attrib; break;
2036 if (strlenW(name) == 0) {
2037 static const WCHAR slashStarW[] = {'\\','*','\0'};
2039 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
2040 strcatW (name, slashStarW);
2043 hff = FindFirstFileW(name, &fd);
2044 if (hff == INVALID_HANDLE_VALUE) {
2045 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
2049 if (attrib_set || attrib_clear) {
2050 fd.dwFileAttributes &= ~attrib_clear;
2051 fd.dwFileAttributes |= attrib_set;
2052 if (!fd.dwFileAttributes)
2053 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
2054 SetFileAttributesW(name, fd.dwFileAttributes);
2056 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
2057 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
2060 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
2063 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
2066 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
2069 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
2072 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
2075 WCMD_output (fmt, flags, fd.cFileName);
2076 for (count=0; count < 8; count++) flags[count] = ' ';
2078 } while (FindNextFileW(hff, &fd) != 0);
2083 /*****************************************************************************
2084 * WCMD_setshow_default
2086 * Set/Show the current default directory
2089 void WCMD_setshow_default (const WCHAR *command) {
2095 WIN32_FIND_DATAW fd;
2097 static const WCHAR parmD[] = {'/','D','\0'};
2099 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2101 /* Skip /D and trailing whitespace if on the front of the command line */
2102 if (CompareStringW(LOCALE_USER_DEFAULT,
2103 NORM_IGNORECASE | SORT_STRINGSORT,
2104 command, 2, parmD, -1) == CSTR_EQUAL) {
2106 while (*command && *command==' ') command++;
2109 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2110 if (strlenW(command) == 0) {
2111 strcatW (cwd, newline);
2115 /* Remove any double quotes, which may be in the
2116 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2119 if (*command != '"') *pos++ = *command;
2124 /* Search for appropriate directory */
2125 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2126 hff = FindFirstFileW(string, &fd);
2127 while (hff != INVALID_HANDLE_VALUE) {
2128 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2129 WCHAR fpath[MAX_PATH];
2131 WCHAR dir[MAX_PATH];
2132 WCHAR fname[MAX_PATH];
2133 WCHAR ext[MAX_PATH];
2134 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2136 /* Convert path into actual directory spec */
2137 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2138 WCMD_splitpath(fpath, drive, dir, fname, ext);
2141 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2144 hff = INVALID_HANDLE_VALUE;
2148 /* Step on to next match */
2149 if (FindNextFileW(hff, &fd) == 0) {
2151 hff = INVALID_HANDLE_VALUE;
2156 /* Change to that directory */
2157 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2159 status = SetCurrentDirectoryW(string);
2162 WCMD_print_error ();
2166 /* Save away the actual new directory, to store as current location */
2167 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2169 /* Restore old directory if drive letter would change, and
2170 CD x:\directory /D (or pushd c:\directory) not supplied */
2171 if ((strstrW(quals, parmD) == NULL) &&
2172 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2173 SetCurrentDirectoryW(cwd);
2177 /* Set special =C: type environment variable, for drive letter of
2178 change of directory, even if path was restored due to missing
2179 /D (allows changing drive letter when not resident on that
2181 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2183 strcpyW(env, equalW);
2184 memcpy(env+1, string, 2 * sizeof(WCHAR));
2186 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2187 SetEnvironmentVariableW(env, string);
2194 /****************************************************************************
2197 * Set/Show the system date
2198 * FIXME: Can't change date yet
2201 void WCMD_setshow_date (void) {
2203 WCHAR curdate[64], buffer[64];
2205 static const WCHAR parmT[] = {'/','T','\0'};
2207 if (strlenW(param1) == 0) {
2208 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2209 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2210 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2211 if (strstrW (quals, parmT) == NULL) {
2212 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2213 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2214 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2216 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2220 else WCMD_print_error ();
2223 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2227 /****************************************************************************
2230 static int WCMD_compare( const void *a, const void *b )
2233 const WCHAR * const *str_a = a, * const *str_b = b;
2234 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2235 *str_a, -1, *str_b, -1 );
2236 if( r == CSTR_LESS_THAN ) return -1;
2237 if( r == CSTR_GREATER_THAN ) return 1;
2241 /****************************************************************************
2242 * WCMD_setshow_sortenv
2244 * sort variables into order for display
2245 * Optionally only display those who start with a stub
2246 * returns the count displayed
2248 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2250 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2253 if (stub) stublen = strlenW(stub);
2255 /* count the number of strings, and the total length */
2257 len += (strlenW(&s[len]) + 1);
2261 /* add the strings to an array */
2262 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2266 for( i=1; i<count; i++ )
2267 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2269 /* sort the array */
2270 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2273 for( i=0; i<count; i++ ) {
2274 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2275 NORM_IGNORECASE | SORT_STRINGSORT,
2276 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2277 /* Don't display special internal variables */
2278 if (str[i][0] != '=') {
2279 WCMD_output_asis(str[i]);
2280 WCMD_output_asis(newline);
2287 return displayedcount;
2290 /****************************************************************************
2293 * Set/Show the environment variables
2296 void WCMD_setshow_env (WCHAR *s) {
2301 static const WCHAR parmP[] = {'/','P','\0'};
2303 if (param1[0] == 0x00 && quals[0] == 0x00) {
2304 env = GetEnvironmentStringsW();
2305 WCMD_setshow_sortenv( env, NULL );
2309 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2310 if (CompareStringW(LOCALE_USER_DEFAULT,
2311 NORM_IGNORECASE | SORT_STRINGSORT,
2312 s, 2, parmP, -1) == CSTR_EQUAL) {
2313 WCHAR string[MAXSTRING];
2317 while (*s && *s==' ') s++;
2319 WCMD_opt_s_strip_quotes(s);
2321 /* If no parameter, or no '=' sign, return an error */
2322 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2323 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2327 /* Output the prompt */
2329 if (strlenW(p) != 0) WCMD_output(p);
2331 /* Read the reply */
2332 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2333 sizeof(string)/sizeof(WCHAR), &count, NULL);
2335 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2336 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2337 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2338 wine_dbgstr_w(string));
2339 status = SetEnvironmentVariableW(s, string);
2346 WCMD_opt_s_strip_quotes(s);
2347 p = strchrW (s, '=');
2349 env = GetEnvironmentStringsW();
2350 if (WCMD_setshow_sortenv( env, s ) == 0) {
2351 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2358 if (strlenW(p) == 0) p = NULL;
2359 status = SetEnvironmentVariableW(s, p);
2360 gle = GetLastError();
2361 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2363 } else if ((!status)) WCMD_print_error();
2367 /****************************************************************************
2370 * Set/Show the path environment variable
2373 void WCMD_setshow_path (const WCHAR *command) {
2377 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2378 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2380 if (strlenW(param1) == 0) {
2381 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2383 WCMD_output_asis ( pathEqW);
2384 WCMD_output_asis ( string);
2385 WCMD_output_asis ( newline);
2388 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2392 if (*command == '=') command++; /* Skip leading '=' */
2393 status = SetEnvironmentVariableW(pathW, command);
2394 if (!status) WCMD_print_error();
2398 /****************************************************************************
2399 * WCMD_setshow_prompt
2401 * Set or show the command prompt.
2404 void WCMD_setshow_prompt (void) {
2407 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2409 if (strlenW(param1) == 0) {
2410 SetEnvironmentVariableW(promptW, NULL);
2414 while ((*s == '=') || (*s == ' ')) s++;
2415 if (strlenW(s) == 0) {
2416 SetEnvironmentVariableW(promptW, NULL);
2418 else SetEnvironmentVariableW(promptW, s);
2422 /****************************************************************************
2425 * Set/Show the system time
2426 * FIXME: Can't change time yet
2429 void WCMD_setshow_time (void) {
2431 WCHAR curtime[64], buffer[64];
2434 static const WCHAR parmT[] = {'/','T','\0'};
2436 if (strlenW(param1) == 0) {
2438 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2439 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2440 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2441 if (strstrW (quals, parmT) == NULL) {
2442 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2443 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2444 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2446 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2450 else WCMD_print_error ();
2453 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2457 /****************************************************************************
2460 * Shift batch parameters.
2461 * Optional /n says where to start shifting (n=0-8)
2464 void WCMD_shift (const WCHAR *command) {
2467 if (context != NULL) {
2468 WCHAR *pos = strchrW(command, '/');
2473 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2474 start = (*(pos+1) - '0');
2476 SetLastError(ERROR_INVALID_PARAMETER);
2481 WINE_TRACE("Shifting variables, starting at %d\n", start);
2482 for (i=start;i<=8;i++) {
2483 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2485 context -> shift_count[9] = context -> shift_count[9] + 1;
2490 /****************************************************************************
2493 * Set the console title
2495 void WCMD_title (const WCHAR *command) {
2496 SetConsoleTitleW(command);
2499 /****************************************************************************
2502 * Copy a file to standard output.
2505 void WCMD_type (WCHAR *command) {
2508 WCHAR *argN = command;
2509 BOOL writeHeaders = FALSE;
2511 if (param1[0] == 0x00) {
2512 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2516 if (param2[0] != 0x00) writeHeaders = TRUE;
2518 /* Loop through all args */
2521 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2529 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2530 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2531 FILE_ATTRIBUTE_NORMAL, NULL);
2532 if (h == INVALID_HANDLE_VALUE) {
2533 WCMD_print_error ();
2534 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2538 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2539 WCMD_output(fmt, thisArg);
2541 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2542 if (count == 0) break; /* ReadFile reports success on EOF! */
2544 WCMD_output_asis (buffer);
2551 /****************************************************************************
2554 * Output either a file or stdin to screen in pages
2557 void WCMD_more (WCHAR *command) {
2560 WCHAR *argN = command;
2562 WCHAR moreStrPage[100];
2565 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2566 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2567 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2568 ')',' ','-','-','\n','\0'};
2569 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2571 /* Prefix the NLS more with '-- ', then load the text */
2573 strcpyW(moreStr, moreStart);
2574 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2575 (sizeof(moreStr)/sizeof(WCHAR))-3);
2577 if (param1[0] == 0x00) {
2579 /* Wine implements pipes via temporary files, and hence stdin is
2580 effectively reading from the file. This means the prompts for
2581 more are satisfied by the next line from the input (file). To
2582 avoid this, ensure stdin is to the console */
2583 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2584 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2585 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2586 FILE_ATTRIBUTE_NORMAL, 0);
2587 WINE_TRACE("No parms - working probably in pipe mode\n");
2588 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2590 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2591 once you get in this bit unless due to a pipe, its going to end badly... */
2592 wsprintfW(moreStrPage, moreFmt, moreStr);
2594 WCMD_enter_paged_mode(moreStrPage);
2595 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2596 if (count == 0) break; /* ReadFile reports success on EOF! */
2598 WCMD_output_asis (buffer);
2600 WCMD_leave_paged_mode();
2602 /* Restore stdin to what it was */
2603 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2604 CloseHandle(hConIn);
2608 BOOL needsPause = FALSE;
2610 /* Loop through all args */
2611 WINE_TRACE("Parms supplied - working through each file\n");
2612 WCMD_enter_paged_mode(moreStrPage);
2615 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2623 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2624 WCMD_leave_paged_mode();
2625 WCMD_output_asis(moreStrPage);
2626 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2627 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2628 WCMD_enter_paged_mode(moreStrPage);
2632 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2633 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2634 FILE_ATTRIBUTE_NORMAL, NULL);
2635 if (h == INVALID_HANDLE_VALUE) {
2636 WCMD_print_error ();
2637 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2641 ULONG64 fileLen = 0;
2642 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2644 /* Get the file size */
2645 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2646 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2649 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2650 if (count == 0) break; /* ReadFile reports success on EOF! */
2654 /* Update % count (would be used in WCMD_output_asis as prompt) */
2655 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2657 WCMD_output_asis (buffer);
2663 WCMD_leave_paged_mode();
2667 /****************************************************************************
2670 * Display verify flag.
2671 * FIXME: We don't actually do anything with the verify flag other than toggle
2675 void WCMD_verify (const WCHAR *command) {
2679 count = strlenW(command);
2681 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2682 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2685 if (lstrcmpiW(command, onW) == 0) {
2689 else if (lstrcmpiW(command, offW) == 0) {
2693 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2696 /****************************************************************************
2699 * Display version info.
2702 void WCMD_version (void) {
2704 WCMD_output (version_string);
2708 /****************************************************************************
2711 * Display volume info and/or set volume label. Returns 0 if error.
2714 int WCMD_volume (int mode, const WCHAR *path) {
2716 DWORD count, serial;
2717 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2720 if (strlenW(path) == 0) {
2721 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2723 WCMD_print_error ();
2726 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2727 &serial, NULL, NULL, NULL, 0);
2730 static const WCHAR fmt[] = {'%','s','\\','\0'};
2731 if ((path[1] != ':') || (strlenW(path) != 2)) {
2732 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2735 wsprintfW (curdir, fmt, path);
2736 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2741 WCMD_print_error ();
2744 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2745 curdir[0], label, HIWORD(serial), LOWORD(serial));
2747 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2748 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2749 sizeof(string)/sizeof(WCHAR), &count, NULL);
2751 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2752 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2754 if (strlenW(path) != 0) {
2755 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2758 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2764 /**************************************************************************
2767 * Exit either the process, or just this batch program
2771 void WCMD_exit (CMD_LIST **cmdList) {
2773 static const WCHAR parmB[] = {'/','B','\0'};
2774 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2776 if (context && lstrcmpiW(quals, parmB) == 0) {
2778 context -> skip_rest = TRUE;
2786 /*****************************************************************************
2789 * Lists or sets file associations (assoc = TRUE)
2790 * Lists or sets file types (assoc = FALSE)
2792 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2795 DWORD accessOptions = KEY_READ;
2797 LONG rc = ERROR_SUCCESS;
2798 WCHAR keyValue[MAXSTRING];
2799 DWORD valueLen = MAXSTRING;
2801 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2802 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2804 /* See if parameter includes '=' */
2806 newValue = strchrW(command, '=');
2807 if (newValue) accessOptions |= KEY_WRITE;
2809 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2810 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2811 accessOptions, &key) != ERROR_SUCCESS) {
2812 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2816 /* If no parameters then list all associations */
2817 if (*command == 0x00) {
2820 /* Enumerate all the keys */
2821 while (rc != ERROR_NO_MORE_ITEMS) {
2822 WCHAR keyName[MAXSTRING];
2825 /* Find the next value */
2826 nameLen = MAXSTRING;
2827 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2829 if (rc == ERROR_SUCCESS) {
2831 /* Only interested in extension ones if assoc, or others
2833 if ((keyName[0] == '.' && assoc) ||
2834 (!(keyName[0] == '.') && (!assoc)))
2836 WCHAR subkey[MAXSTRING];
2837 strcpyW(subkey, keyName);
2838 if (!assoc) strcatW(subkey, shOpCmdW);
2840 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2842 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2843 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2844 WCMD_output_asis(keyName);
2845 WCMD_output_asis(equalW);
2846 /* If no default value found, leave line empty after '=' */
2847 if (rc == ERROR_SUCCESS) {
2848 WCMD_output_asis(keyValue);
2850 WCMD_output_asis(newline);
2851 RegCloseKey(readKey);
2859 /* Parameter supplied - if no '=' on command line, its a query */
2860 if (newValue == NULL) {
2862 WCHAR subkey[MAXSTRING];
2864 /* Query terminates the parameter at the first space */
2865 strcpyW(keyValue, command);
2866 space = strchrW(keyValue, ' ');
2867 if (space) *space=0x00;
2869 /* Set up key name */
2870 strcpyW(subkey, keyValue);
2871 if (!assoc) strcatW(subkey, shOpCmdW);
2873 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2875 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2876 WCMD_output_asis(command);
2877 WCMD_output_asis(equalW);
2878 /* If no default value found, leave line empty after '=' */
2879 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2880 WCMD_output_asis(newline);
2881 RegCloseKey(readKey);
2884 WCHAR msgbuffer[MAXSTRING];
2885 WCHAR outbuffer[MAXSTRING];
2887 /* Load the translated 'File association not found' */
2889 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2891 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2893 wsprintfW(outbuffer, msgbuffer, keyValue);
2894 WCMD_output_asis(outbuffer);
2898 /* Not a query - its a set or clear of a value */
2901 WCHAR subkey[MAXSTRING];
2903 /* Get pointer to new value */
2907 /* Set up key name */
2908 strcpyW(subkey, command);
2909 if (!assoc) strcatW(subkey, shOpCmdW);
2911 /* If nothing after '=' then clear value - only valid for ASSOC */
2912 if (*newValue == 0x00) {
2914 if (assoc) rc = RegDeleteKeyW(key, command);
2915 if (assoc && rc == ERROR_SUCCESS) {
2916 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2918 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2923 WCHAR msgbuffer[MAXSTRING];
2924 WCHAR outbuffer[MAXSTRING];
2926 /* Load the translated 'File association not found' */
2928 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2929 sizeof(msgbuffer)/sizeof(WCHAR));
2931 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2932 sizeof(msgbuffer)/sizeof(WCHAR));
2934 wsprintfW(outbuffer, msgbuffer, keyValue);
2935 WCMD_output_asis(outbuffer);
2939 /* It really is a set value = contents */
2941 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2942 accessOptions, NULL, &readKey, NULL);
2943 if (rc == ERROR_SUCCESS) {
2944 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2945 (LPBYTE)newValue, strlenW(newValue));
2946 RegCloseKey(readKey);
2949 if (rc != ERROR_SUCCESS) {
2953 WCMD_output_asis(command);
2954 WCMD_output_asis(equalW);
2955 WCMD_output_asis(newValue);
2956 WCMD_output_asis(newline);
2966 /****************************************************************************
2969 * Clear the terminal screen.
2972 void WCMD_color (void) {
2974 /* Emulate by filling the screen from the top left to bottom right with
2975 spaces, then moving the cursor to the top left afterwards */
2976 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2977 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2979 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2980 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2984 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2990 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2995 /* Convert the color hex digits */
2996 if (param1[0] == 0x00) {
2997 color = defaultColor;
2999 color = strtoulW(param1, NULL, 16);
3002 /* Fail if fg == bg color */
3003 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
3008 /* Set the current screen contents and ensure all future writes
3009 remain this color */
3010 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
3011 SetConsoleTextAttribute(hStdOut, color);