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 struct env_stack *saved_environment;
45 struct env_stack *pushd_directories;
47 extern HINSTANCE hinst;
48 extern WCHAR inbuilt[][10];
49 extern int defaultColor;
50 extern BOOL echo_mode;
51 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
52 extern BATCH_CONTEXT *context;
53 extern DWORD errorlevel;
55 static BOOL verify_mode = FALSE;
57 const WCHAR dotW[] = {'.','\0'};
58 const WCHAR dotdotW[] = {'.','.','\0'};
59 const WCHAR nullW[] = {'\0'};
60 const WCHAR starW[] = {'*','\0'};
61 const WCHAR slashW[] = {'\\','\0'};
62 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'};
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 (const WCHAR *message, BOOL showSureText,
81 const BOOL *optionAll) {
83 WCHAR msgbuffer[MAXSTRING];
84 WCHAR Ybuffer[MAXSTRING];
85 WCHAR Nbuffer[MAXSTRING];
86 WCHAR Abuffer[MAXSTRING];
87 WCHAR answer[MAX_PATH] = {'\0'};
90 /* Load the translated 'Are you sure', plus valid answers */
91 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
92 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
93 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
94 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
96 /* Loop waiting on a Y or N */
97 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
98 static const WCHAR startBkt[] = {' ','(','\0'};
99 static const WCHAR endBkt[] = {')','?','\0'};
101 WCMD_output_asis (message);
103 WCMD_output_asis (msgbuffer);
105 WCMD_output_asis (startBkt);
106 WCMD_output_asis (Ybuffer);
107 WCMD_output_asis (fslashW);
108 WCMD_output_asis (Nbuffer);
110 WCMD_output_asis (fslashW);
111 WCMD_output_asis (Abuffer);
113 WCMD_output_asis (endBkt);
114 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, sizeof(answer)/sizeof(WCHAR), &count);
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 (const 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_skip_leading_spaces((WCHAR*) command));
193 ptr = WCMD_skip_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_skip_leading_spaces(&ptr[1]);
218 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
223 ptr = WCMD_skip_leading_spaces(&ptr[2]);
228 ptr = WCMD_skip_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_skip_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);
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 (and, if needed, any intermediate directories).
478 * Modifies its argument by replacing slashes temporarily with nulls.
481 static BOOL create_full_path(WCHAR* path)
485 /* don't mess with drive letter portion of path, if any */
490 /* Strip trailing slashes. */
491 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
494 /* Step through path, creating intermediate directories as needed. */
495 /* First component includes drive letter, if any. */
499 /* Skip to end of component */
500 while (*p == '\\') p++;
501 while (*p && *p != '\\') p++;
503 /* path is now the original full path */
504 return CreateDirectoryW(path, NULL);
506 /* Truncate path, create intermediate directory, and restore path */
508 rv = CreateDirectoryW(path, NULL);
510 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
517 void WCMD_create_dir (WCHAR *command) {
519 WCHAR *argN = command;
521 if (param1[0] == 0x00) {
522 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
525 /* Loop through all args */
527 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL);
529 if (!create_full_path(thisArg)) {
536 /* Parse the /A options given by the user on the commandline
537 * into a bitmask of wanted attributes (*wantSet),
538 * and a bitmask of unwanted attributes (*wantClear).
540 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
541 static const WCHAR parmA[] = {'/','A','\0'};
544 /* both are strictly 'out' parameters */
548 /* For each /A argument */
549 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
553 /* Skip optional : */
556 /* For each of the attribute specifier chars to this /A option */
557 for (; *p != 0 && *p != '/'; p++) {
566 /* Convert the attribute specifier to a bit in one of the masks */
568 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
569 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
570 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
571 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
573 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
583 /* If filename part of parameter is * or *.*,
584 * and neither /Q nor /P options were given,
585 * prompt the user whether to proceed.
586 * Returns FALSE if user says no, TRUE otherwise.
587 * *pPrompted is set to TRUE if the user is prompted.
588 * (If /P supplied, del will prompt for individual files later.)
590 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
591 static const WCHAR parmP[] = {'/','P','\0'};
592 static const WCHAR parmQ[] = {'/','Q','\0'};
594 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
595 static const WCHAR anyExt[]= {'.','*','\0'};
598 WCHAR fname[MAX_PATH];
600 WCHAR fpath[MAX_PATH];
602 /* Convert path into actual directory spec */
603 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
604 WCMD_splitpath(fpath, drive, dir, fname, ext);
606 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
607 if ((strcmpW(fname, starW) == 0) &&
608 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
610 WCHAR question[MAXSTRING];
611 static const WCHAR fmt[] = {'%','s',' ','\0'};
613 /* Caller uses this to suppress "file not found" warning later */
616 /* Ask for confirmation */
617 wsprintfW(question, fmt, fpath);
618 return WCMD_ask_confirm(question, TRUE, NULL);
621 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
625 /* Helper function for WCMD_delete().
626 * Deletes a single file, directory, or wildcard.
627 * If /S was given, does it recursively.
628 * Returns TRUE if a file was deleted.
630 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
632 static const WCHAR parmP[] = {'/','P','\0'};
633 static const WCHAR parmS[] = {'/','S','\0'};
634 static const WCHAR parmF[] = {'/','F','\0'};
636 DWORD unwanted_attrs;
638 WCHAR argCopy[MAX_PATH];
641 WCHAR fpath[MAX_PATH];
643 BOOL handleParm = TRUE;
645 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
647 strcpyW(argCopy, thisArg);
648 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
649 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
651 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
652 /* Skip this arg if user declines to delete *.* */
656 /* First, try to delete in the current directory */
657 hff = FindFirstFileW(argCopy, &fd);
658 if (hff == INVALID_HANDLE_VALUE) {
664 /* Support del <dirname> by just deleting all files dirname\* */
666 && (strchrW(argCopy,'*') == NULL)
667 && (strchrW(argCopy,'?') == NULL)
668 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
670 WCHAR modifiedParm[MAX_PATH];
671 static const WCHAR slashStar[] = {'\\','*','\0'};
673 strcpyW(modifiedParm, argCopy);
674 strcatW(modifiedParm, slashStar);
677 WCMD_delete_one(modifiedParm);
679 } else if (handleParm) {
681 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
682 strcpyW (fpath, argCopy);
684 p = strrchrW (fpath, '\\');
687 strcatW (fpath, fd.cFileName);
689 else strcpyW (fpath, fd.cFileName);
690 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
693 /* Handle attribute matching (/A) */
694 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
695 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
697 /* /P means prompt for each file */
698 if (ok && strstrW (quals, parmP) != NULL) {
699 WCHAR question[MAXSTRING];
701 /* Ask for confirmation */
702 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
703 ok = WCMD_ask_confirm(question, FALSE, NULL);
706 /* Only proceed if ok to */
709 /* If file is read only, and /A:r or /F supplied, delete it */
710 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
711 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
712 strstrW (quals, parmF) != NULL)) {
713 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
716 /* Now do the delete */
717 if (!DeleteFileW(fpath)) WCMD_print_error ();
721 } while (FindNextFileW(hff, &fd) != 0);
725 /* Now recurse into all subdirectories handling the parameter in the same way */
726 if (strstrW (quals, parmS) != NULL) {
728 WCHAR thisDir[MAX_PATH];
733 WCHAR fname[MAX_PATH];
736 /* Convert path into actual directory spec */
737 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
738 WCMD_splitpath(thisDir, drive, dir, fname, ext);
740 strcpyW(thisDir, drive);
741 strcatW(thisDir, dir);
742 cPos = strlenW(thisDir);
744 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
746 /* Append '*' to the directory */
748 thisDir[cPos+1] = 0x00;
750 hff = FindFirstFileW(thisDir, &fd);
752 /* Remove residual '*' */
753 thisDir[cPos] = 0x00;
755 if (hff != INVALID_HANDLE_VALUE) {
756 DIRECTORY_STACK *allDirs = NULL;
757 DIRECTORY_STACK *lastEntry = NULL;
760 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
761 (strcmpW(fd.cFileName, dotdotW) != 0) &&
762 (strcmpW(fd.cFileName, dotW) != 0)) {
764 DIRECTORY_STACK *nextDir;
765 WCHAR subParm[MAX_PATH];
767 /* Work out search parameter in sub dir */
768 strcpyW (subParm, thisDir);
769 strcatW (subParm, fd.cFileName);
770 strcatW (subParm, slashW);
771 strcatW (subParm, fname);
772 strcatW (subParm, ext);
773 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
775 /* Allocate memory, add to list */
776 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
777 if (allDirs == NULL) allDirs = nextDir;
778 if (lastEntry != NULL) lastEntry->next = nextDir;
780 nextDir->next = NULL;
781 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
782 (strlenW(subParm)+1) * sizeof(WCHAR));
783 strcpyW(nextDir->dirName, subParm);
785 } while (FindNextFileW(hff, &fd) != 0);
788 /* Go through each subdir doing the delete */
789 while (allDirs != NULL) {
790 DIRECTORY_STACK *tempDir;
792 tempDir = allDirs->next;
793 found |= WCMD_delete_one (allDirs->dirName);
795 HeapFree(GetProcessHeap(),0,allDirs->dirName);
796 HeapFree(GetProcessHeap(),0,allDirs);
805 /****************************************************************************
808 * Delete a file or wildcarded set.
811 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
812 * - Each set is a pattern, eg /ahr /as-r means
813 * readonly+hidden OR nonreadonly system files
814 * - The '-' applies to a single field, ie /a:-hr means read only
818 BOOL WCMD_delete (WCHAR *command) {
821 BOOL argsProcessed = FALSE;
822 BOOL foundAny = FALSE;
826 for (argno=0; ; argno++) {
831 thisArg = WCMD_parameter (command, argno, &argN, NULL);
833 break; /* no more parameters */
835 continue; /* skip options */
837 argsProcessed = TRUE;
838 found = WCMD_delete_one(thisArg);
841 WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
846 /* Handle no valid args */
848 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
856 * Returns a trimmed version of s with all leading and trailing whitespace removed
860 static WCHAR *WCMD_strtrim(const WCHAR *s)
862 DWORD len = strlenW(s);
863 const WCHAR *start = s;
866 if (!(result = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR))))
869 while (isspaceW(*start)) start++;
871 const WCHAR *end = s + len - 1;
872 while (end > start && isspaceW(*end)) end--;
873 memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
874 result[end - start + 1] = '\0';
882 /****************************************************************************
885 * Echo input to the screen (or not). We don't try to emulate the bugs
886 * in DOS (try typing "ECHO ON AGAIN" for an example).
889 void WCMD_echo (const WCHAR *command)
892 const WCHAR *origcommand = command;
895 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
896 || command[0]==':' || command[0]==';')
899 trimmed = WCMD_strtrim(command);
900 if (!trimmed) return;
902 count = strlenW(trimmed);
903 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
904 && origcommand[0]!=';') {
905 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
906 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
910 if (lstrcmpiW(trimmed, onW) == 0)
912 else if (lstrcmpiW(trimmed, offW) == 0)
915 WCMD_output_asis (command);
916 WCMD_output (newline);
918 HeapFree(GetProcessHeap(), 0, trimmed);
921 /*****************************************************************************
924 * Execute a command, and any && or bracketed follow on to the command. The
925 * first command to be executed may not be at the front of the
926 * commands->thiscommand string (eg. it may point after a DO or ELSE)
928 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
929 const WCHAR *variable, const WCHAR *value,
930 BOOL isIF, BOOL conditionTRUE)
932 CMD_LIST *curPosition = *cmdList;
933 int myDepth = (*cmdList)->bracketDepth;
935 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
936 cmdList, wine_dbgstr_w(firstcmd),
937 wine_dbgstr_w(variable), wine_dbgstr_w(value),
940 /* Skip leading whitespace between condition and the command */
941 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
943 /* Process the first command, if there is one */
944 if (conditionTRUE && firstcmd && *firstcmd) {
945 WCHAR *command = WCMD_strdupW(firstcmd);
946 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
947 HeapFree(GetProcessHeap(), 0, command);
951 /* If it didn't move the position, step to next command */
952 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
954 /* Process any other parts of the command */
956 BOOL processThese = TRUE;
958 if (isIF) processThese = conditionTRUE;
961 static const WCHAR ifElse[] = {'e','l','s','e'};
963 /* execute all appropriate commands */
964 curPosition = *cmdList;
966 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
968 (*cmdList)->prevDelim,
969 (*cmdList)->bracketDepth, myDepth);
971 /* Execute any statements appended to the line */
972 /* FIXME: Only if previous call worked for && or failed for || */
973 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
974 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
975 if (processThese && (*cmdList)->command) {
976 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
979 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
981 /* Execute any appended to the statement with (...) */
982 } else if ((*cmdList)->bracketDepth > myDepth) {
984 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
985 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
987 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
989 /* End of the command - does 'ELSE ' follow as the next command? */
992 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
993 (*cmdList)->command)) {
995 /* Swap between if and else processing */
996 processThese = !processThese;
998 /* Process the ELSE part */
1000 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1001 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1003 /* Skip leading whitespace between condition and the command */
1004 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1006 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1009 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1011 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1020 /**************************************************************************
1023 * Batch file loop processing.
1025 * On entry: cmdList contains the syntax up to the set
1026 * next cmdList and all in that bracket contain the set data
1027 * next cmdlist contains the DO cmd
1028 * following that is either brackets or && entries (as per if)
1032 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
1034 WIN32_FIND_DATAW fd;
1037 static const WCHAR inW[] = {'i','n'};
1038 static const WCHAR doW[] = {'d','o'};
1039 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
1045 BOOL expandDirs = FALSE;
1046 BOOL useNumbers = FALSE;
1047 BOOL doFileset = FALSE;
1048 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
1050 CMD_LIST *thisCmdStart;
1053 /* Handle optional qualifiers (multiple are allowed) */
1054 while (*curPos && *curPos == '/') {
1055 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
1057 switch (toupperW(*curPos)) {
1058 case 'D': curPos++; expandDirs = TRUE; break;
1059 case 'L': curPos++; useNumbers = TRUE; break;
1061 /* Recursive is special case - /R can have an optional path following it */
1062 /* filenamesets are another special case - /F can have an optional options following it */
1066 BOOL isRecursive = (*curPos == 'R');
1071 /* Skip whitespace */
1073 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1075 /* Next parm is either qualifier, path/options or variable -
1076 only care about it if it is the path/options */
1077 if (*curPos && *curPos != '/' && *curPos != '%') {
1078 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
1080 static unsigned int once;
1081 if (!once++) WINE_FIXME("/F needs to handle options\n");
1087 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
1091 /* Skip whitespace between qualifiers */
1092 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1095 /* Skip whitespace before variable */
1096 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1098 /* Ensure line continues with variable */
1099 if (!*curPos || *curPos != '%') {
1100 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1104 /* Variable should follow */
1106 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
1107 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
1109 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
1110 curPos = &curPos[i];
1112 /* Skip whitespace before IN */
1113 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
1115 /* Ensure line continues with IN */
1117 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
1119 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1123 /* Save away where the set of data starts and the variable */
1124 thisDepth = (*cmdList)->bracketDepth;
1125 *cmdList = (*cmdList)->nextcommand;
1126 setStart = (*cmdList);
1128 /* Skip until the close bracket */
1129 WINE_TRACE("Searching %p as the set\n", *cmdList);
1131 (*cmdList)->command != NULL &&
1132 (*cmdList)->bracketDepth > thisDepth) {
1133 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1134 *cmdList = (*cmdList)->nextcommand;
1137 /* Skip the close bracket, if there is one */
1138 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1140 /* Syntax error if missing close bracket, or nothing following it
1141 and once we have the complete set, we expect a DO */
1142 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1143 if ((*cmdList == NULL)
1144 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1146 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1150 /* Save away the starting position for the commands (and offset for the
1152 cmdStart = *cmdList;
1154 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1158 /* Loop through all set entries */
1160 thisSet->command != NULL &&
1161 thisSet->bracketDepth >= thisDepth) {
1163 /* Loop through all entries on the same line */
1167 WINE_TRACE("Processing for set %p\n", thisSet);
1169 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1172 * If the parameter within the set has a wildcard then search for matching files
1173 * otherwise do a literal substitution.
1175 static const WCHAR wildcards[] = {'*','?','\0'};
1176 thisCmdStart = cmdStart;
1179 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1181 if (!useNumbers && !doFileset) {
1182 if (strpbrkW (item, wildcards)) {
1183 hff = FindFirstFileW(item, &fd);
1184 if (hff != INVALID_HANDLE_VALUE) {
1186 BOOL isDirectory = FALSE;
1188 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1190 /* Handle as files or dirs appropriately, but ignore . and .. */
1191 if (isDirectory == expandDirs &&
1192 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1193 (strcmpW(fd.cFileName, dotW) != 0))
1195 thisCmdStart = cmdStart;
1196 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1197 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1198 fd.cFileName, FALSE, TRUE);
1201 } while (FindNextFileW(hff, &fd) != 0);
1205 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1208 } else if (useNumbers) {
1209 /* Convert the first 3 numbers to signed longs and save */
1210 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1211 /* else ignore them! */
1213 /* Filesets - either a list of files, or a command to run and parse the output */
1214 } else if (doFileset && *itemStart != '"') {
1217 WCHAR temp_file[MAX_PATH];
1219 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1220 wine_dbgstr_w(item));
1222 /* If backquote or single quote, we need to launch that command
1223 and parse the results - use a temporary file */
1224 if (*itemStart == '`' || *itemStart == '\'') {
1226 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1227 static const WCHAR redirOut[] = {'>','%','s','\0'};
1228 static const WCHAR cmdW[] = {'C','M','D','\0'};
1230 /* Remove trailing character */
1231 itemStart[strlenW(itemStart)-1] = 0x00;
1233 /* Get temp filename */
1234 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1235 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1237 /* Execute program and redirect output */
1238 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1239 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1241 /* Open the file, read line by line and process */
1242 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1243 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1246 /* Open the file, read line by line and process */
1247 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1248 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1251 /* Process the input file */
1252 if (input == INVALID_HANDLE_VALUE) {
1253 WCMD_print_error ();
1254 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
1256 return; /* FOR loop aborts at first failure here */
1260 WCHAR buffer[MAXSTRING] = {'\0'};
1261 WCHAR *where, *parm;
1263 while (WCMD_fgets(buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1265 /* Skip blank lines*/
1266 parm = WCMD_parameter (buffer, 0, &where, NULL);
1267 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1268 wine_dbgstr_w(buffer));
1271 /* FIXME: The following should be moved into its own routine and
1272 reused for the string literal parsing below */
1273 thisCmdStart = cmdStart;
1274 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1275 cmdEnd = thisCmdStart;
1281 CloseHandle (input);
1284 /* Delete the temporary file */
1285 if (*itemStart == '`' || *itemStart == '\'') {
1286 DeleteFileW(temp_file);
1289 /* Filesets - A string literal */
1290 } else if (doFileset && *itemStart == '"') {
1291 WCHAR buffer[MAXSTRING] = {'\0'};
1292 WCHAR *where, *parm;
1294 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1295 strcpyW(buffer, item);
1296 parm = WCMD_parameter (buffer, 0, &where, NULL);
1297 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1298 wine_dbgstr_w(buffer));
1301 /* FIXME: The following should be moved into its own routine and
1302 reused for the string literal parsing below */
1303 thisCmdStart = cmdStart;
1304 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1305 cmdEnd = thisCmdStart;
1309 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1310 cmdEnd = thisCmdStart;
1314 /* Move onto the next set line */
1315 thisSet = thisSet->nextcommand;
1318 /* If /L is provided, now run the for loop */
1321 static const WCHAR fmt[] = {'%','d','\0'};
1323 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1324 numbers[0], numbers[2], numbers[1]);
1326 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1329 sprintfW(thisNum, fmt, i);
1330 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1332 thisCmdStart = cmdStart;
1333 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1334 cmdEnd = thisCmdStart;
1338 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1339 all processing, OR it should be pointing to the end of && processing OR
1340 it should be pointing at the NULL end of bracket for the DO. The return
1341 value needs to be the NEXT command to execute, which it either is, or
1342 we need to step over the closing bracket */
1344 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1347 /**************************************************************************
1350 * Simple on-line help. Help text is stored in the resource file.
1353 void WCMD_give_help (const WCHAR *command)
1357 command = WCMD_skip_leading_spaces((WCHAR*) command);
1358 if (strlenW(command) == 0) {
1359 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1362 /* Display help message for builtin commands */
1363 for (i=0; i<=WCMD_EXIT; i++) {
1364 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1365 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1366 WCMD_output_asis (WCMD_LoadMessage(i));
1370 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1371 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1372 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1373 command, -1, externals[i], -1) == CSTR_EQUAL) {
1375 static const WCHAR helpW[] = {' ', '/','?','\0'};
1376 strcpyW(cmd, command);
1377 strcatW(cmd, helpW);
1378 WCMD_run_program(cmd, 0);
1382 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1387 /****************************************************************************
1390 * Batch file jump instruction. Not the most efficient algorithm ;-)
1391 * Prints error message if the specified label cannot be found - the file pointer is
1392 * then at EOF, effectively stopping the batch file.
1393 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1396 void WCMD_goto (CMD_LIST **cmdList) {
1398 WCHAR string[MAX_PATH];
1399 WCHAR current[MAX_PATH];
1401 /* Do not process any more parts of a processed multipart or multilines command */
1402 if (cmdList) *cmdList = NULL;
1404 if (context != NULL) {
1405 WCHAR *paramStart = param1, *str;
1406 static const WCHAR eofW[] = {':','e','o','f','\0'};
1408 if (param1[0] == 0x00) {
1409 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1413 /* Handle special :EOF label */
1414 if (lstrcmpiW (eofW, param1) == 0) {
1415 context -> skip_rest = TRUE;
1419 /* Support goto :label as well as goto label */
1420 if (*paramStart == ':') paramStart++;
1422 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1423 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1425 while (isspaceW (*str)) str++;
1429 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1432 /* ignore space at the end */
1434 if (lstrcmpiW (current, paramStart) == 0) return;
1437 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
1442 /*****************************************************************************
1445 * Push a directory onto the stack
1448 void WCMD_pushd (const WCHAR *command)
1450 struct env_stack *curdir;
1452 static const WCHAR parmD[] = {'/','D','\0'};
1454 if (strchrW(command, '/') != NULL) {
1455 SetLastError(ERROR_INVALID_PARAMETER);
1460 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1461 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1462 if( !curdir || !thisdir ) {
1465 WINE_ERR ("out of memory\n");
1469 /* Change directory using CD code with /D parameter */
1470 strcpyW(quals, parmD);
1471 GetCurrentDirectoryW (1024, thisdir);
1473 WCMD_setshow_default(command);
1479 curdir -> next = pushd_directories;
1480 curdir -> strings = thisdir;
1481 if (pushd_directories == NULL) {
1482 curdir -> u.stackdepth = 1;
1484 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1486 pushd_directories = curdir;
1491 /*****************************************************************************
1494 * Pop a directory from the stack
1497 void WCMD_popd (void) {
1498 struct env_stack *temp = pushd_directories;
1500 if (!pushd_directories)
1503 /* pop the old environment from the stack, and make it the current dir */
1504 pushd_directories = temp->next;
1505 SetCurrentDirectoryW(temp->strings);
1506 LocalFree (temp->strings);
1510 /****************************************************************************
1513 * Batch file conditional.
1515 * On entry, cmdlist will point to command containing the IF, and optionally
1516 * the first command to execute (if brackets not found)
1517 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1518 * If ('s were found, execute all within that bracket
1519 * Command may optionally be followed by an ELSE - need to skip instructions
1520 * in the else using the same logic
1522 * FIXME: Much more syntax checking needed!
1525 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1527 int negate; /* Negate condition */
1528 int test; /* Condition evaluation result */
1529 WCHAR condition[MAX_PATH], *command, *s;
1530 static const WCHAR notW[] = {'n','o','t','\0'};
1531 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1532 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1533 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1534 static const WCHAR eqeqW[] = {'=','=','\0'};
1535 static const WCHAR parmI[] = {'/','I','\0'};
1536 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1538 negate = !lstrcmpiW(param1,notW);
1539 strcpyW(condition, (negate ? param2 : param1));
1540 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1542 if (!lstrcmpiW (condition, errlvlW)) {
1543 WCHAR *param = WCMD_parameter(p, 1+negate, NULL, NULL);
1545 long int param_int = strtolW(param, &endptr, 10);
1547 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1550 test = ((long int)errorlevel >= param_int);
1551 WCMD_parameter(p, 2+negate, &command, NULL);
1553 else if (!lstrcmpiW (condition, existW)) {
1554 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1555 WCMD_parameter(p, 2+negate, &command, NULL);
1557 else if (!lstrcmpiW (condition, defdW)) {
1558 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1559 WCMD_parameter(p, 2+negate, &command, NULL);
1561 else if ((s = strstrW (p, eqeqW))) {
1562 /* We need to get potential surrounding double quotes, so param1/2 can't be used */
1563 WCHAR *leftPart, *leftPartEnd, *rightPart, *rightPartEnd;
1565 WCMD_parameter(p, 0+negate+caseInsensitive, &leftPart, &leftPartEnd);
1566 WCMD_parameter(p, 1+negate+caseInsensitive, &rightPart, &rightPartEnd);
1567 test = caseInsensitive
1568 ? (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
1569 leftPart, leftPartEnd-leftPart+1,
1570 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL)
1571 : (CompareStringW(LOCALE_SYSTEM_DEFAULT, 0,
1572 leftPart, leftPartEnd-leftPart+1,
1573 rightPart, rightPartEnd-rightPart+1) == CSTR_EQUAL);
1574 WCMD_parameter(s, 1, &command, NULL);
1577 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
1581 /* Process rest of IF statement which is on the same line
1582 Note: This may process all or some of the cmdList (eg a GOTO) */
1583 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1586 /****************************************************************************
1589 * Move a file, directory tree or wildcarded set of files.
1592 void WCMD_move (void)
1595 WIN32_FIND_DATAW fd;
1597 WCHAR input[MAX_PATH];
1598 WCHAR output[MAX_PATH];
1600 WCHAR dir[MAX_PATH];
1601 WCHAR fname[MAX_PATH];
1602 WCHAR ext[MAX_PATH];
1604 if (param1[0] == 0x00) {
1605 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1609 /* If no destination supplied, assume current directory */
1610 if (param2[0] == 0x00) {
1611 strcpyW(param2, dotW);
1614 /* If 2nd parm is directory, then use original filename */
1615 /* Convert partial path to full path */
1616 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1617 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1618 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1619 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1621 /* Split into components */
1622 WCMD_splitpath(input, drive, dir, fname, ext);
1624 hff = FindFirstFileW(input, &fd);
1625 if (hff == INVALID_HANDLE_VALUE)
1629 WCHAR dest[MAX_PATH];
1630 WCHAR src[MAX_PATH];
1634 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1636 /* Build src & dest name */
1637 strcpyW(src, drive);
1640 /* See if dest is an existing directory */
1641 attribs = GetFileAttributesW(output);
1642 if (attribs != INVALID_FILE_ATTRIBUTES &&
1643 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1644 strcpyW(dest, output);
1645 strcatW(dest, slashW);
1646 strcatW(dest, fd.cFileName);
1648 strcpyW(dest, output);
1651 strcatW(src, fd.cFileName);
1653 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1654 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1656 /* If destination exists, prompt unless /Y supplied */
1657 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1659 WCHAR copycmd[MAXSTRING];
1662 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1663 if (strstrW (quals, parmNoY))
1665 else if (strstrW (quals, parmY))
1668 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1669 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1670 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1671 && ! lstrcmpiW (copycmd, parmY));
1674 /* Prompt if overwriting */
1676 WCHAR question[MAXSTRING];
1679 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1681 /* Ask for confirmation */
1682 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1683 ok = WCMD_ask_confirm(question, FALSE, NULL);
1685 /* So delete the destination prior to the move */
1687 if (!DeleteFileW(dest)) {
1688 WCMD_print_error ();
1697 status = MoveFileW(src, dest);
1699 status = 1; /* Anything other than 0 to prevent error msg below */
1703 WCMD_print_error ();
1706 } while (FindNextFileW(hff, &fd) != 0);
1711 /****************************************************************************
1714 * Suspend execution of a batch script until a key is typed
1717 void WCMD_pause (void)
1723 HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
1725 have_console = GetConsoleMode(hIn, &oldmode);
1727 SetConsoleMode(hIn, 0);
1729 WCMD_output(anykey);
1730 WCMD_ReadFile(hIn, &key, 1, &count);
1732 SetConsoleMode(hIn, oldmode);
1735 /****************************************************************************
1738 * Delete a directory.
1741 void WCMD_remove_dir (WCHAR *command) {
1744 int argsProcessed = 0;
1745 WCHAR *argN = command;
1746 static const WCHAR parmS[] = {'/','S','\0'};
1747 static const WCHAR parmQ[] = {'/','Q','\0'};
1749 /* Loop through all args */
1751 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1752 if (argN && argN[0] != '/') {
1753 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1754 wine_dbgstr_w(quals));
1757 /* If subdirectory search not supplied, just try to remove
1758 and report error if it fails (eg if it contains a file) */
1759 if (strstrW (quals, parmS) == NULL) {
1760 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1762 /* Otherwise use ShFileOp to recursively remove a directory */
1765 SHFILEOPSTRUCTW lpDir;
1768 if (strstrW (quals, parmQ) == NULL) {
1770 WCHAR question[MAXSTRING];
1771 static const WCHAR fmt[] = {'%','s',' ','\0'};
1773 /* Ask for confirmation */
1774 wsprintfW(question, fmt, thisArg);
1775 ok = WCMD_ask_confirm(question, TRUE, NULL);
1777 /* Abort if answer is 'N' */
1784 lpDir.pFrom = thisArg;
1785 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1786 lpDir.wFunc = FO_DELETE;
1787 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1792 /* Handle no valid args */
1793 if (argsProcessed == 0) {
1794 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1800 /****************************************************************************
1806 void WCMD_rename (void)
1810 WIN32_FIND_DATAW fd;
1811 WCHAR input[MAX_PATH];
1812 WCHAR *dotDst = NULL;
1814 WCHAR dir[MAX_PATH];
1815 WCHAR fname[MAX_PATH];
1816 WCHAR ext[MAX_PATH];
1820 /* Must be at least two args */
1821 if (param1[0] == 0x00 || param2[0] == 0x00) {
1822 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
1827 /* Destination cannot contain a drive letter or directory separator */
1828 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1829 SetLastError(ERROR_INVALID_PARAMETER);
1835 /* Convert partial path to full path */
1836 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1837 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1838 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1839 dotDst = strchrW(param2, '.');
1841 /* Split into components */
1842 WCMD_splitpath(input, drive, dir, fname, ext);
1844 hff = FindFirstFileW(input, &fd);
1845 if (hff == INVALID_HANDLE_VALUE)
1849 WCHAR dest[MAX_PATH];
1850 WCHAR src[MAX_PATH];
1851 WCHAR *dotSrc = NULL;
1854 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1856 /* FIXME: If dest name or extension is *, replace with filename/ext
1857 part otherwise use supplied name. This supports:
1859 ren jim.* fred.* etc
1860 However, windows has a more complex algorithm supporting eg
1861 ?'s and *'s mid name */
1862 dotSrc = strchrW(fd.cFileName, '.');
1864 /* Build src & dest name */
1865 strcpyW(src, drive);
1868 dirLen = strlenW(src);
1869 strcatW(src, fd.cFileName);
1872 if (param2[0] == '*') {
1873 strcatW(dest, fd.cFileName);
1874 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1876 strcatW(dest, param2);
1877 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1880 /* Build Extension */
1881 if (dotDst && (*(dotDst+1)=='*')) {
1882 if (dotSrc) strcatW(dest, dotSrc);
1883 } else if (dotDst) {
1884 if (dotDst) strcatW(dest, dotDst);
1887 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1888 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1890 status = MoveFileW(src, dest);
1893 WCMD_print_error ();
1896 } while (FindNextFileW(hff, &fd) != 0);
1901 /*****************************************************************************
1904 * Make a copy of the environment.
1906 static WCHAR *WCMD_dupenv( const WCHAR *env )
1916 len += (strlenW(&env[len]) + 1);
1918 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1921 WINE_ERR("out of memory\n");
1924 memcpy (env_copy, env, len*sizeof (WCHAR));
1930 /*****************************************************************************
1933 * setlocal pushes the environment onto a stack
1934 * Save the environment as unicode so we don't screw anything up.
1936 void WCMD_setlocal (const WCHAR *s) {
1938 struct env_stack *env_copy;
1939 WCHAR cwd[MAX_PATH];
1941 /* DISABLEEXTENSIONS ignored */
1943 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1946 WINE_ERR ("out of memory\n");
1950 env = GetEnvironmentStringsW ();
1952 env_copy->strings = WCMD_dupenv (env);
1953 if (env_copy->strings)
1955 env_copy->next = saved_environment;
1956 saved_environment = env_copy;
1958 /* Save the current drive letter */
1959 GetCurrentDirectoryW(MAX_PATH, cwd);
1960 env_copy->u.cwd = cwd[0];
1963 LocalFree (env_copy);
1965 FreeEnvironmentStringsW (env);
1969 /*****************************************************************************
1972 * endlocal pops the environment off a stack
1973 * Note: When searching for '=', search from WCHAR position 1, to handle
1974 * special internal environment variables =C:, =D: etc
1976 void WCMD_endlocal (void) {
1977 WCHAR *env, *old, *p;
1978 struct env_stack *temp;
1981 if (!saved_environment)
1984 /* pop the old environment from the stack */
1985 temp = saved_environment;
1986 saved_environment = temp->next;
1988 /* delete the current environment, totally */
1989 env = GetEnvironmentStringsW ();
1990 old = WCMD_dupenv (GetEnvironmentStringsW ());
1993 n = strlenW(&old[len]) + 1;
1994 p = strchrW(&old[len] + 1, '=');
1998 SetEnvironmentVariableW (&old[len], NULL);
2003 FreeEnvironmentStringsW (env);
2005 /* restore old environment */
2006 env = temp->strings;
2009 n = strlenW(&env[len]) + 1;
2010 p = strchrW(&env[len] + 1, '=');
2014 SetEnvironmentVariableW (&env[len], p);
2019 /* Restore current drive letter */
2020 if (IsCharAlphaW(temp->u.cwd)) {
2022 WCHAR cwd[MAX_PATH];
2023 static const WCHAR fmt[] = {'=','%','c',':','\0'};
2025 wsprintfW(envvar, fmt, temp->u.cwd);
2026 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
2027 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
2028 SetCurrentDirectoryW(cwd);
2036 /*****************************************************************************
2037 * WCMD_setshow_default
2039 * Set/Show the current default directory
2042 void WCMD_setshow_default (const WCHAR *command) {
2048 WIN32_FIND_DATAW fd;
2050 static const WCHAR parmD[] = {'/','D','\0'};
2052 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2054 /* Skip /D and trailing whitespace if on the front of the command line */
2055 if (CompareStringW(LOCALE_USER_DEFAULT,
2056 NORM_IGNORECASE | SORT_STRINGSORT,
2057 command, 2, parmD, -1) == CSTR_EQUAL) {
2059 while (*command && (*command==' ' || *command=='\t'))
2063 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2064 if (strlenW(command) == 0) {
2065 strcatW (cwd, newline);
2069 /* Remove any double quotes, which may be in the
2070 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2073 if (*command != '"') *pos++ = *command;
2076 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2080 /* Search for appropriate directory */
2081 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2082 hff = FindFirstFileW(string, &fd);
2083 if (hff != INVALID_HANDLE_VALUE) {
2085 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2086 WCHAR fpath[MAX_PATH];
2088 WCHAR dir[MAX_PATH];
2089 WCHAR fname[MAX_PATH];
2090 WCHAR ext[MAX_PATH];
2091 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2093 /* Convert path into actual directory spec */
2094 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2095 WCMD_splitpath(fpath, drive, dir, fname, ext);
2098 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2101 } while (FindNextFileW(hff, &fd) != 0);
2105 /* Change to that directory */
2106 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2108 status = SetCurrentDirectoryW(string);
2111 WCMD_print_error ();
2115 /* Save away the actual new directory, to store as current location */
2116 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2118 /* Restore old directory if drive letter would change, and
2119 CD x:\directory /D (or pushd c:\directory) not supplied */
2120 if ((strstrW(quals, parmD) == NULL) &&
2121 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2122 SetCurrentDirectoryW(cwd);
2126 /* Set special =C: type environment variable, for drive letter of
2127 change of directory, even if path was restored due to missing
2128 /D (allows changing drive letter when not resident on that
2130 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2132 strcpyW(env, equalW);
2133 memcpy(env+1, string, 2 * sizeof(WCHAR));
2135 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2136 SetEnvironmentVariableW(env, string);
2143 /****************************************************************************
2146 * Set/Show the system date
2147 * FIXME: Can't change date yet
2150 void WCMD_setshow_date (void) {
2152 WCHAR curdate[64], buffer[64];
2154 static const WCHAR parmT[] = {'/','T','\0'};
2156 if (strlenW(param1) == 0) {
2157 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2158 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2159 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2160 if (strstrW (quals, parmT) == NULL) {
2161 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2162 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2164 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2168 else WCMD_print_error ();
2171 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2175 /****************************************************************************
2178 static int WCMD_compare( const void *a, const void *b )
2181 const WCHAR * const *str_a = a, * const *str_b = b;
2182 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2183 *str_a, -1, *str_b, -1 );
2184 if( r == CSTR_LESS_THAN ) return -1;
2185 if( r == CSTR_GREATER_THAN ) return 1;
2189 /****************************************************************************
2190 * WCMD_setshow_sortenv
2192 * sort variables into order for display
2193 * Optionally only display those who start with a stub
2194 * returns the count displayed
2196 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2198 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2201 if (stub) stublen = strlenW(stub);
2203 /* count the number of strings, and the total length */
2205 len += (strlenW(&s[len]) + 1);
2209 /* add the strings to an array */
2210 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2214 for( i=1; i<count; i++ )
2215 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2217 /* sort the array */
2218 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2221 for( i=0; i<count; i++ ) {
2222 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2223 NORM_IGNORECASE | SORT_STRINGSORT,
2224 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2225 /* Don't display special internal variables */
2226 if (str[i][0] != '=') {
2227 WCMD_output_asis(str[i]);
2228 WCMD_output_asis(newline);
2235 return displayedcount;
2238 /****************************************************************************
2241 * Set/Show the environment variables
2244 void WCMD_setshow_env (WCHAR *s) {
2249 static const WCHAR parmP[] = {'/','P','\0'};
2251 if (param1[0] == 0x00 && quals[0] == 0x00) {
2252 env = GetEnvironmentStringsW();
2253 WCMD_setshow_sortenv( env, NULL );
2257 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2258 if (CompareStringW(LOCALE_USER_DEFAULT,
2259 NORM_IGNORECASE | SORT_STRINGSORT,
2260 s, 2, parmP, -1) == CSTR_EQUAL) {
2261 WCHAR string[MAXSTRING];
2265 while (*s && (*s==' ' || *s=='\t')) s++;
2267 WCMD_strip_quotes(s);
2269 /* If no parameter, or no '=' sign, return an error */
2270 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2271 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2275 /* Output the prompt */
2277 if (strlenW(p) != 0) WCMD_output(p);
2279 /* Read the reply */
2280 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2282 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2283 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2284 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2285 wine_dbgstr_w(string));
2286 status = SetEnvironmentVariableW(s, string);
2293 WCMD_strip_quotes(s);
2294 p = strchrW (s, '=');
2296 env = GetEnvironmentStringsW();
2297 if (WCMD_setshow_sortenv( env, s ) == 0) {
2298 WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
2305 if (strlenW(p) == 0) p = NULL;
2306 status = SetEnvironmentVariableW(s, p);
2307 gle = GetLastError();
2308 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2310 } else if ((!status)) WCMD_print_error();
2314 /****************************************************************************
2317 * Set/Show the path environment variable
2320 void WCMD_setshow_path (const WCHAR *command) {
2324 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2325 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2327 if (strlenW(param1) == 0) {
2328 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2330 WCMD_output_asis ( pathEqW);
2331 WCMD_output_asis ( string);
2332 WCMD_output_asis ( newline);
2335 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
2339 if (*command == '=') command++; /* Skip leading '=' */
2340 status = SetEnvironmentVariableW(pathW, command);
2341 if (!status) WCMD_print_error();
2345 /****************************************************************************
2346 * WCMD_setshow_prompt
2348 * Set or show the command prompt.
2351 void WCMD_setshow_prompt (void) {
2354 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2356 if (strlenW(param1) == 0) {
2357 SetEnvironmentVariableW(promptW, NULL);
2361 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2362 if (strlenW(s) == 0) {
2363 SetEnvironmentVariableW(promptW, NULL);
2365 else SetEnvironmentVariableW(promptW, s);
2369 /****************************************************************************
2372 * Set/Show the system time
2373 * FIXME: Can't change time yet
2376 void WCMD_setshow_time (void) {
2378 WCHAR curtime[64], buffer[64];
2381 static const WCHAR parmT[] = {'/','T','\0'};
2383 if (strlenW(param1) == 0) {
2385 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2386 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2387 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2388 if (strstrW (quals, parmT) == NULL) {
2389 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2390 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2392 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2396 else WCMD_print_error ();
2399 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2403 /****************************************************************************
2406 * Shift batch parameters.
2407 * Optional /n says where to start shifting (n=0-8)
2410 void WCMD_shift (const WCHAR *command) {
2413 if (context != NULL) {
2414 WCHAR *pos = strchrW(command, '/');
2419 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2420 start = (*(pos+1) - '0');
2422 SetLastError(ERROR_INVALID_PARAMETER);
2427 WINE_TRACE("Shifting variables, starting at %d\n", start);
2428 for (i=start;i<=8;i++) {
2429 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2431 context -> shift_count[9] = context -> shift_count[9] + 1;
2436 /****************************************************************************
2439 * Set the console title
2441 void WCMD_title (const WCHAR *command) {
2442 SetConsoleTitleW(command);
2445 /****************************************************************************
2448 * Copy a file to standard output.
2451 void WCMD_type (WCHAR *command) {
2454 WCHAR *argN = command;
2455 BOOL writeHeaders = FALSE;
2457 if (param1[0] == 0x00) {
2458 WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
2462 if (param2[0] != 0x00) writeHeaders = TRUE;
2464 /* Loop through all args */
2467 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2475 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2476 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2477 FILE_ATTRIBUTE_NORMAL, NULL);
2478 if (h == INVALID_HANDLE_VALUE) {
2479 WCMD_print_error ();
2480 WCMD_output(WCMD_LoadMessage(WCMD_READFAIL), thisArg); /* should be _stderr */
2484 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2485 WCMD_output(fmt, thisArg);
2487 while (WCMD_ReadFile(h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count)) {
2488 if (count == 0) break; /* ReadFile reports success on EOF! */
2490 WCMD_output_asis (buffer);
2497 /****************************************************************************
2500 * Output either a file or stdin to screen in pages
2503 void WCMD_more (WCHAR *command) {
2506 WCHAR *argN = command;
2508 WCHAR moreStrPage[100];
2511 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2512 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2513 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2514 ')',' ','-','-','\n','\0'};
2515 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2517 /* Prefix the NLS more with '-- ', then load the text */
2519 strcpyW(moreStr, moreStart);
2520 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2521 (sizeof(moreStr)/sizeof(WCHAR))-3);
2523 if (param1[0] == 0x00) {
2525 /* Wine implements pipes via temporary files, and hence stdin is
2526 effectively reading from the file. This means the prompts for
2527 more are satisfied by the next line from the input (file). To
2528 avoid this, ensure stdin is to the console */
2529 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2530 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2531 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2532 FILE_ATTRIBUTE_NORMAL, 0);
2533 WINE_TRACE("No parms - working probably in pipe mode\n");
2534 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2536 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2537 once you get in this bit unless due to a pipe, its going to end badly... */
2538 wsprintfW(moreStrPage, moreFmt, moreStr);
2540 WCMD_enter_paged_mode(moreStrPage);
2541 while (WCMD_ReadFile(hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2542 if (count == 0) break; /* ReadFile reports success on EOF! */
2544 WCMD_output_asis (buffer);
2546 WCMD_leave_paged_mode();
2548 /* Restore stdin to what it was */
2549 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2550 CloseHandle(hConIn);
2554 BOOL needsPause = FALSE;
2556 /* Loop through all args */
2557 WINE_TRACE("Parms supplied - working through each file\n");
2558 WCMD_enter_paged_mode(moreStrPage);
2561 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2569 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2570 WCMD_leave_paged_mode();
2571 WCMD_output_asis(moreStrPage);
2572 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, sizeof(buffer)/sizeof(WCHAR), &count);
2573 WCMD_enter_paged_mode(moreStrPage);
2577 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2578 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2579 FILE_ATTRIBUTE_NORMAL, NULL);
2580 if (h == INVALID_HANDLE_VALUE) {
2581 WCMD_print_error ();
2582 WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2586 ULONG64 fileLen = 0;
2587 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2589 /* Get the file size */
2590 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2591 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2594 while (WCMD_ReadFile(h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count)) {
2595 if (count == 0) break; /* ReadFile reports success on EOF! */
2599 /* Update % count (would be used in WCMD_output_asis as prompt) */
2600 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2602 WCMD_output_asis (buffer);
2608 WCMD_leave_paged_mode();
2612 /****************************************************************************
2615 * Display verify flag.
2616 * FIXME: We don't actually do anything with the verify flag other than toggle
2620 void WCMD_verify (const WCHAR *command) {
2624 count = strlenW(command);
2626 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2627 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2630 if (lstrcmpiW(command, onW) == 0) {
2634 else if (lstrcmpiW(command, offW) == 0) {
2635 verify_mode = FALSE;
2638 else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
2641 /****************************************************************************
2644 * Display version info.
2647 void WCMD_version (void) {
2649 WCMD_output (version_string);
2653 /****************************************************************************
2656 * Display volume information (set_label = FALSE)
2657 * Additionally set volume label (set_label = TRUE)
2658 * Returns 1 on success, 0 otherwise
2661 int WCMD_volume(BOOL set_label, const WCHAR *path)
2663 DWORD count, serial;
2664 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2667 if (strlenW(path) == 0) {
2668 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2670 WCMD_print_error ();
2673 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2674 &serial, NULL, NULL, NULL, 0);
2677 static const WCHAR fmt[] = {'%','s','\\','\0'};
2678 if ((path[1] != ':') || (strlenW(path) != 2)) {
2679 WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
2682 wsprintfW (curdir, fmt, path);
2683 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2688 WCMD_print_error ();
2691 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2692 curdir[0], label, HIWORD(serial), LOWORD(serial));
2694 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2695 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, sizeof(string)/sizeof(WCHAR), &count);
2697 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2698 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2700 if (strlenW(path) != 0) {
2701 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2704 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2710 /**************************************************************************
2713 * Exit either the process, or just this batch program
2717 void WCMD_exit (CMD_LIST **cmdList) {
2719 static const WCHAR parmB[] = {'/','B','\0'};
2720 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2722 if (context && lstrcmpiW(quals, parmB) == 0) {
2724 context -> skip_rest = TRUE;
2732 /*****************************************************************************
2735 * Lists or sets file associations (assoc = TRUE)
2736 * Lists or sets file types (assoc = FALSE)
2738 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2741 DWORD accessOptions = KEY_READ;
2743 LONG rc = ERROR_SUCCESS;
2744 WCHAR keyValue[MAXSTRING];
2745 DWORD valueLen = MAXSTRING;
2747 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2748 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2750 /* See if parameter includes '=' */
2752 newValue = strchrW(command, '=');
2753 if (newValue) accessOptions |= KEY_WRITE;
2755 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2756 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2757 accessOptions, &key) != ERROR_SUCCESS) {
2758 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2762 /* If no parameters then list all associations */
2763 if (*command == 0x00) {
2766 /* Enumerate all the keys */
2767 while (rc != ERROR_NO_MORE_ITEMS) {
2768 WCHAR keyName[MAXSTRING];
2771 /* Find the next value */
2772 nameLen = MAXSTRING;
2773 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2775 if (rc == ERROR_SUCCESS) {
2777 /* Only interested in extension ones if assoc, or others
2779 if ((keyName[0] == '.' && assoc) ||
2780 (!(keyName[0] == '.') && (!assoc)))
2782 WCHAR subkey[MAXSTRING];
2783 strcpyW(subkey, keyName);
2784 if (!assoc) strcatW(subkey, shOpCmdW);
2786 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2788 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2789 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2790 WCMD_output_asis(keyName);
2791 WCMD_output_asis(equalW);
2792 /* If no default value found, leave line empty after '=' */
2793 if (rc == ERROR_SUCCESS) {
2794 WCMD_output_asis(keyValue);
2796 WCMD_output_asis(newline);
2797 RegCloseKey(readKey);
2805 /* Parameter supplied - if no '=' on command line, its a query */
2806 if (newValue == NULL) {
2808 WCHAR subkey[MAXSTRING];
2810 /* Query terminates the parameter at the first space */
2811 strcpyW(keyValue, command);
2812 space = strchrW(keyValue, ' ');
2813 if (space) *space=0x00;
2815 /* Set up key name */
2816 strcpyW(subkey, keyValue);
2817 if (!assoc) strcatW(subkey, shOpCmdW);
2819 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2821 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2822 WCMD_output_asis(command);
2823 WCMD_output_asis(equalW);
2824 /* If no default value found, leave line empty after '=' */
2825 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2826 WCMD_output_asis(newline);
2827 RegCloseKey(readKey);
2830 WCHAR msgbuffer[MAXSTRING];
2831 WCHAR outbuffer[MAXSTRING];
2833 /* Load the translated 'File association not found' */
2835 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2837 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2839 wsprintfW(outbuffer, msgbuffer, keyValue);
2840 WCMD_output_asis_stderr(outbuffer);
2844 /* Not a query - its a set or clear of a value */
2847 WCHAR subkey[MAXSTRING];
2849 /* Get pointer to new value */
2853 /* Set up key name */
2854 strcpyW(subkey, command);
2855 if (!assoc) strcatW(subkey, shOpCmdW);
2857 /* If nothing after '=' then clear value - only valid for ASSOC */
2858 if (*newValue == 0x00) {
2860 if (assoc) rc = RegDeleteKeyW(key, command);
2861 if (assoc && rc == ERROR_SUCCESS) {
2862 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2864 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2869 WCHAR msgbuffer[MAXSTRING];
2870 WCHAR outbuffer[MAXSTRING];
2872 /* Load the translated 'File association not found' */
2874 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2875 sizeof(msgbuffer)/sizeof(WCHAR));
2877 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2878 sizeof(msgbuffer)/sizeof(WCHAR));
2880 wsprintfW(outbuffer, msgbuffer, keyValue);
2881 WCMD_output_asis_stderr(outbuffer);
2885 /* It really is a set value = contents */
2887 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2888 accessOptions, NULL, &readKey, NULL);
2889 if (rc == ERROR_SUCCESS) {
2890 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2892 sizeof(WCHAR) * (strlenW(newValue) + 1));
2893 RegCloseKey(readKey);
2896 if (rc != ERROR_SUCCESS) {
2900 WCMD_output_asis(command);
2901 WCMD_output_asis(equalW);
2902 WCMD_output_asis(newValue);
2903 WCMD_output_asis(newline);
2913 /****************************************************************************
2916 * Colors the terminal screen.
2919 void WCMD_color (void) {
2921 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2922 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2924 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2925 WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
2929 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2935 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2940 /* Convert the color hex digits */
2941 if (param1[0] == 0x00) {
2942 color = defaultColor;
2944 color = strtoulW(param1, NULL, 16);
2947 /* Fail if fg == bg color */
2948 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2953 /* Set the current screen contents and ensure all future writes
2954 remain this color */
2955 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2956 SetConsoleTextAttribute(hStdOut, color);