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 const WCHAR externals[][10] = {
71 {'A','T','T','R','I','B','\0'}
74 /**************************************************************************
77 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
80 * Returns True if Y (or A) answer is selected
81 * If optionAll contains a pointer, ALL is allowed, and if answered
85 static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
86 const BOOL *optionAll) {
88 WCHAR msgbuffer[MAXSTRING];
89 WCHAR Ybuffer[MAXSTRING];
90 WCHAR Nbuffer[MAXSTRING];
91 WCHAR Abuffer[MAXSTRING];
92 WCHAR answer[MAX_PATH] = {'\0'};
95 /* Load the translated 'Are you sure', plus valid answers */
96 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
97 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
98 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
99 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
101 /* Loop waiting on a Y or N */
102 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
103 static const WCHAR startBkt[] = {' ','(','\0'};
104 static const WCHAR endBkt[] = {')','?','\0'};
106 WCMD_output_asis (message);
108 WCMD_output_asis (msgbuffer);
110 WCMD_output_asis (startBkt);
111 WCMD_output_asis (Ybuffer);
112 WCMD_output_asis (fslashW);
113 WCMD_output_asis (Nbuffer);
115 WCMD_output_asis (fslashW);
116 WCMD_output_asis (Abuffer);
118 WCMD_output_asis (endBkt);
119 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
120 sizeof(answer)/sizeof(WCHAR), &count, NULL);
121 answer[0] = toupperW(answer[0]);
124 /* Return the answer */
125 return ((answer[0] == Ybuffer[0]) ||
126 (optionAll && (answer[0] == Abuffer[0])));
129 /****************************************************************************
132 * Clear the terminal screen.
135 void WCMD_clear_screen (void) {
137 /* Emulate by filling the screen from the top left to bottom right with
138 spaces, then moving the cursor to the top left afterwards */
139 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
140 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
142 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
147 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
151 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
152 SetConsoleCursorPosition(hStdOut, topLeft);
156 /****************************************************************************
159 * Change the default i/o device (ie redirect STDin/STDout).
162 void WCMD_change_tty (void) {
164 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
168 /****************************************************************************
173 void WCMD_choice (const WCHAR * command) {
175 static const WCHAR bellW[] = {7,0};
176 static const WCHAR commaW[] = {',',0};
177 static const WCHAR bracket_open[] = {'[',0};
178 static const WCHAR bracket_close[] = {']','?',0};
183 WCHAR *my_command = NULL;
184 WCHAR opt_default = 0;
185 DWORD opt_timeout = 0;
192 have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
195 my_command = WCMD_strdupW(WCMD_skip_leading_spaces((WCHAR*) command));
199 ptr = WCMD_skip_leading_spaces(my_command);
200 while (*ptr == '/') {
201 switch (toupperW(ptr[1])) {
204 /* the colon is optional */
208 if (!*ptr || isspaceW(*ptr)) {
209 WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
210 HeapFree(GetProcessHeap(), 0, my_command);
214 /* remember the allowed keys (overwrite previous /C option) */
216 while (*ptr && (!isspaceW(*ptr)))
220 /* terminate allowed chars */
222 ptr = WCMD_skip_leading_spaces(&ptr[1]);
224 WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
229 ptr = WCMD_skip_leading_spaces(&ptr[2]);
234 ptr = WCMD_skip_leading_spaces(&ptr[2]);
239 /* the colon is optional */
243 opt_default = *ptr++;
245 if (!opt_default || (*ptr != ',')) {
246 WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
247 HeapFree(GetProcessHeap(), 0, my_command);
253 while (((answer[count] = *ptr)) && isdigitW(*ptr) && (count < 15)) {
259 opt_timeout = atoiW(answer);
261 ptr = WCMD_skip_leading_spaces(ptr);
265 WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
266 HeapFree(GetProcessHeap(), 0, my_command);
272 WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
275 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
277 /* use default keys, when needed: localized versions of "Y"es and "No" */
279 LoadStringW(hinst, WCMD_YES, buffer, sizeof(buffer)/sizeof(WCHAR));
280 LoadStringW(hinst, WCMD_NO, buffer + 1, sizeof(buffer)/sizeof(WCHAR) - 1);
285 /* print the question, when needed */
287 WCMD_output_asis(ptr);
291 WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
295 /* print a list of all allowed answers inside brackets */
296 WCMD_output_asis(bracket_open);
299 while ((answer[0] = *ptr++)) {
300 WCMD_output_asis(answer);
302 WCMD_output_asis(commaW);
304 WCMD_output_asis(bracket_close);
309 /* FIXME: Add support for option /T */
310 WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count, NULL);
313 answer[0] = toupperW(answer[0]);
315 ptr = strchrW(opt_c, answer[0]);
317 WCMD_output_asis(answer);
318 WCMD_output(newline);
320 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
322 errorlevel = (ptr - opt_c) + 1;
323 WINE_TRACE("answer: %d\n", errorlevel);
324 HeapFree(GetProcessHeap(), 0, my_command);
329 /* key not allowed: play the bell */
330 WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
331 WCMD_output_asis(bellW);
336 /****************************************************************************
339 * Copy a file or wildcarded set.
340 * FIXME: Add support for a+b+c type syntax
343 void WCMD_copy (void) {
348 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
350 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
351 BOOL copyToDir = FALSE;
352 WCHAR srcspec[MAX_PATH];
356 WCHAR fname[MAX_PATH];
359 if (param1[0] == 0x00) {
360 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
364 /* Convert source into full spec */
365 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
366 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
367 if (srcpath[strlenW(srcpath) - 1] == '\\')
368 srcpath[strlenW(srcpath) - 1] = '\0';
370 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
371 attribs = GetFileAttributesW(srcpath);
375 strcpyW(srcspec, srcpath);
377 /* If a directory, then add \* on the end when searching */
378 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
379 strcatW(srcpath, slashW);
380 strcatW(srcspec, slashW);
381 strcatW(srcspec, starW);
383 WCMD_splitpath(srcpath, drive, dir, fname, ext);
384 strcpyW(srcpath, drive);
385 strcatW(srcpath, dir);
388 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
390 /* If no destination supplied, assume current directory */
391 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
392 if (param2[0] == 0x00) {
393 strcpyW(param2, dotW);
396 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
397 if (outpath[strlenW(outpath) - 1] == '\\')
398 outpath[strlenW(outpath) - 1] = '\0';
399 attribs = GetFileAttributesW(outpath);
400 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
401 strcatW (outpath, slashW);
404 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
405 wine_dbgstr_w(outpath), copyToDir);
407 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
408 if (strstrW (quals, parmNoY))
410 else if (strstrW (quals, parmY))
413 /* By default, we will force the overwrite in batch mode and ask for
414 * confirmation in interactive mode. */
417 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
418 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
419 * default behavior. */
420 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
421 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
422 if (!lstrcmpiW (copycmd, parmY))
424 else if (!lstrcmpiW (copycmd, parmNoY))
429 /* Loop through all source files */
430 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
431 hff = FindFirstFileW(srcspec, &fd);
432 if (hff != INVALID_HANDLE_VALUE) {
434 WCHAR outname[MAX_PATH];
435 WCHAR srcname[MAX_PATH];
436 BOOL overwrite = force;
438 /* Destination is either supplied filename, or source name in
439 supplied destination directory */
440 strcpyW(outname, outpath);
441 if (copyToDir) strcatW(outname, fd.cFileName);
442 strcpyW(srcname, srcpath);
443 strcatW(srcname, fd.cFileName);
445 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
446 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
448 /* Skip . and .., and directories */
449 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
451 WINE_TRACE("Skipping directories\n");
454 /* Prompt before overwriting */
455 else if (!overwrite) {
456 attribs = GetFileAttributesW(outname);
457 if (attribs != INVALID_FILE_ATTRIBUTES) {
458 WCHAR buffer[MAXSTRING];
459 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
460 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
462 else overwrite = TRUE;
465 /* Do the copy as appropriate */
467 status = CopyFileW(srcname, outname, FALSE);
468 if (!status) WCMD_print_error ();
471 } while (FindNextFileW(hff, &fd) != 0);
474 status = ERROR_FILE_NOT_FOUND;
479 /****************************************************************************
482 * Create a directory (and, if needed, any intermediate directories).
484 * Modifies its argument by replacing slashes temporarily with nulls.
487 static BOOL create_full_path(WCHAR* path)
491 /* don't mess with drive letter portion of path, if any */
496 /* Strip trailing slashes. */
497 for (p = path + strlenW(path) - 1; p != start && *p == '\\'; p--)
500 /* Step through path, creating intermediate directories as needed. */
501 /* First component includes drive letter, if any. */
505 /* Skip to end of component */
506 while (*p == '\\') p++;
507 while (*p && *p != '\\') p++;
509 /* path is now the original full path */
510 return CreateDirectoryW(path, NULL);
512 /* Truncate path, create intermediate directory, and restore path */
514 rv = CreateDirectoryW(path, NULL);
516 if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
523 void WCMD_create_dir (WCHAR *command) {
525 WCHAR *argN = command;
527 if (param1[0] == 0x00) {
528 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
531 /* Loop through all args */
533 WCHAR *thisArg = WCMD_parameter(command, argno++, &argN, NULL);
535 if (!create_full_path(thisArg)) {
542 /* Parse the /A options given by the user on the commandline
543 * into a bitmask of wanted attributes (*wantSet),
544 * and a bitmask of unwanted attributes (*wantClear).
546 static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
547 static const WCHAR parmA[] = {'/','A','\0'};
550 /* both are strictly 'out' parameters */
554 /* For each /A argument */
555 for (p=strstrW(quals, parmA); p != NULL; p=strstrW(p, parmA)) {
559 /* Skip optional : */
562 /* For each of the attribute specifier chars to this /A option */
563 for (; *p != 0 && *p != '/'; p++) {
572 /* Convert the attribute specifier to a bit in one of the masks */
574 case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
575 case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
576 case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
577 case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
579 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
589 /* If filename part of parameter is * or *.*,
590 * and neither /Q nor /P options were given,
591 * prompt the user whether to proceed.
592 * Returns FALSE if user says no, TRUE otherwise.
593 * *pPrompted is set to TRUE if the user is prompted.
594 * (If /P supplied, del will prompt for individual files later.)
596 static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
597 static const WCHAR parmP[] = {'/','P','\0'};
598 static const WCHAR parmQ[] = {'/','Q','\0'};
600 if ((strstrW(quals, parmQ) == NULL) && (strstrW(quals, parmP) == NULL)) {
601 static const WCHAR anyExt[]= {'.','*','\0'};
604 WCHAR fname[MAX_PATH];
606 WCHAR fpath[MAX_PATH];
608 /* Convert path into actual directory spec */
609 GetFullPathNameW(filename, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
610 WCMD_splitpath(fpath, drive, dir, fname, ext);
612 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
613 if ((strcmpW(fname, starW) == 0) &&
614 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
616 WCHAR question[MAXSTRING];
617 static const WCHAR fmt[] = {'%','s',' ','\0'};
619 /* Caller uses this to suppress "file not found" warning later */
622 /* Ask for confirmation */
623 wsprintfW(question, fmt, fpath);
624 return WCMD_ask_confirm(question, TRUE, NULL);
627 /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
631 /* Helper function for WCMD_delete().
632 * Deletes a single file, directory, or wildcard.
633 * If /S was given, does it recursively.
634 * Returns TRUE if a file was deleted.
636 static BOOL WCMD_delete_one (const WCHAR *thisArg) {
638 static const WCHAR parmP[] = {'/','P','\0'};
639 static const WCHAR parmS[] = {'/','S','\0'};
640 static const WCHAR parmF[] = {'/','F','\0'};
642 DWORD unwanted_attrs;
644 WCHAR argCopy[MAX_PATH];
647 WCHAR fpath[MAX_PATH];
649 BOOL handleParm = TRUE;
651 WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
653 strcpyW(argCopy, thisArg);
654 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
655 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
657 if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
658 /* Skip this arg if user declines to delete *.* */
662 /* First, try to delete in the current directory */
663 hff = FindFirstFileW(argCopy, &fd);
664 if (hff == INVALID_HANDLE_VALUE) {
670 /* Support del <dirname> by just deleting all files dirname\* */
672 && (strchrW(argCopy,'*') == NULL)
673 && (strchrW(argCopy,'?') == NULL)
674 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
676 WCHAR modifiedParm[MAX_PATH];
677 static const WCHAR slashStar[] = {'\\','*','\0'};
679 strcpyW(modifiedParm, argCopy);
680 strcatW(modifiedParm, slashStar);
683 WCMD_delete_one(modifiedParm);
685 } else if (handleParm) {
687 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
688 strcpyW (fpath, argCopy);
690 p = strrchrW (fpath, '\\');
693 strcatW (fpath, fd.cFileName);
695 else strcpyW (fpath, fd.cFileName);
696 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
699 /* Handle attribute matching (/A) */
700 ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
701 && ((fd.dwFileAttributes & unwanted_attrs) == 0);
703 /* /P means prompt for each file */
704 if (ok && strstrW (quals, parmP) != NULL) {
705 WCHAR question[MAXSTRING];
707 /* Ask for confirmation */
708 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
709 ok = WCMD_ask_confirm(question, FALSE, NULL);
712 /* Only proceed if ok to */
715 /* If file is read only, and /A:r or /F supplied, delete it */
716 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
717 ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
718 strstrW (quals, parmF) != NULL)) {
719 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
722 /* Now do the delete */
723 if (!DeleteFileW(fpath)) WCMD_print_error ();
727 } while (FindNextFileW(hff, &fd) != 0);
731 /* Now recurse into all subdirectories handling the parameter in the same way */
732 if (strstrW (quals, parmS) != NULL) {
734 WCHAR thisDir[MAX_PATH];
739 WCHAR fname[MAX_PATH];
742 /* Convert path into actual directory spec */
743 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
744 WCMD_splitpath(thisDir, drive, dir, fname, ext);
746 strcpyW(thisDir, drive);
747 strcatW(thisDir, dir);
748 cPos = strlenW(thisDir);
750 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
752 /* Append '*' to the directory */
754 thisDir[cPos+1] = 0x00;
756 hff = FindFirstFileW(thisDir, &fd);
758 /* Remove residual '*' */
759 thisDir[cPos] = 0x00;
761 if (hff != INVALID_HANDLE_VALUE) {
762 DIRECTORY_STACK *allDirs = NULL;
763 DIRECTORY_STACK *lastEntry = NULL;
766 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
767 (strcmpW(fd.cFileName, dotdotW) != 0) &&
768 (strcmpW(fd.cFileName, dotW) != 0)) {
770 DIRECTORY_STACK *nextDir;
771 WCHAR subParm[MAX_PATH];
773 /* Work out search parameter in sub dir */
774 strcpyW (subParm, thisDir);
775 strcatW (subParm, fd.cFileName);
776 strcatW (subParm, slashW);
777 strcatW (subParm, fname);
778 strcatW (subParm, ext);
779 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
781 /* Allocate memory, add to list */
782 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
783 if (allDirs == NULL) allDirs = nextDir;
784 if (lastEntry != NULL) lastEntry->next = nextDir;
786 nextDir->next = NULL;
787 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
788 (strlenW(subParm)+1) * sizeof(WCHAR));
789 strcpyW(nextDir->dirName, subParm);
791 } while (FindNextFileW(hff, &fd) != 0);
794 /* Go through each subdir doing the delete */
795 while (allDirs != NULL) {
796 DIRECTORY_STACK *tempDir;
798 tempDir = allDirs->next;
799 found |= WCMD_delete_one (allDirs->dirName);
801 HeapFree(GetProcessHeap(),0,allDirs->dirName);
802 HeapFree(GetProcessHeap(),0,allDirs);
811 /****************************************************************************
814 * Delete a file or wildcarded set.
817 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
818 * - Each set is a pattern, eg /ahr /as-r means
819 * readonly+hidden OR nonreadonly system files
820 * - The '-' applies to a single field, ie /a:-hr means read only
824 BOOL WCMD_delete (WCHAR *command) {
827 BOOL argsProcessed = FALSE;
828 BOOL foundAny = FALSE;
832 for (argno=0; ; argno++) {
837 thisArg = WCMD_parameter (command, argno, &argN, NULL);
839 break; /* no more parameters */
841 continue; /* skip options */
843 argsProcessed = TRUE;
844 found = WCMD_delete_one(thisArg);
847 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
852 /* Handle no valid args */
854 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
859 /****************************************************************************
862 * Echo input to the screen (or not). We don't try to emulate the bugs
863 * in DOS (try typing "ECHO ON AGAIN" for an example).
866 void WCMD_echo (const WCHAR *command) {
869 const WCHAR *origcommand = command;
871 if ( command[0]==' ' || command[0]=='\t' || command[0]=='.'
872 || command[0]==':' || command[0]==';')
875 count = strlenW(command);
876 if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
877 && origcommand[0]!=';') {
878 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
879 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
882 if (lstrcmpiW(command, onW) == 0) {
886 if (lstrcmpiW(command, offW) == 0) {
890 WCMD_output_asis (command);
891 WCMD_output (newline);
895 /**************************************************************************
898 * Batch file loop processing.
900 * On entry: cmdList contains the syntax up to the set
901 * next cmdList and all in that bracket contain the set data
902 * next cmdlist contains the DO cmd
903 * following that is either brackets or && entries (as per if)
907 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
912 static const WCHAR inW[] = {'i','n'};
913 static const WCHAR doW[] = {'d','o'};
914 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
920 BOOL expandDirs = FALSE;
921 BOOL useNumbers = FALSE;
922 BOOL doFileset = FALSE;
923 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
925 CMD_LIST *thisCmdStart;
928 /* Handle optional qualifiers (multiple are allowed) */
929 while (*curPos && *curPos == '/') {
930 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
932 switch (toupperW(*curPos)) {
933 case 'D': curPos++; expandDirs = TRUE; break;
934 case 'L': curPos++; useNumbers = TRUE; break;
936 /* Recursive is special case - /R can have an optional path following it */
937 /* filenamesets are another special case - /F can have an optional options following it */
941 BOOL isRecursive = (*curPos == 'R');
946 /* Skip whitespace */
948 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
950 /* Next parm is either qualifier, path/options or variable -
951 only care about it if it is the path/options */
952 if (*curPos && *curPos != '/' && *curPos != '%') {
953 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
955 static unsigned int once;
956 if (!once++) WINE_FIXME("/F needs to handle options\n");
962 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
966 /* Skip whitespace between qualifiers */
967 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
970 /* Skip whitespace before variable */
971 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
973 /* Ensure line continues with variable */
974 if (!*curPos || *curPos != '%') {
975 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
979 /* Variable should follow */
981 while (curPos[i] && curPos[i]!=' ' && curPos[i]!='\t') i++;
982 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
984 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
987 /* Skip whitespace before IN */
988 while (*curPos && (*curPos==' ' || *curPos=='\t')) curPos++;
990 /* Ensure line continues with IN */
992 || !WCMD_keyword_ws_found(inW, sizeof(inW)/sizeof(inW[0]), curPos)) {
994 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
998 /* Save away where the set of data starts and the variable */
999 thisDepth = (*cmdList)->bracketDepth;
1000 *cmdList = (*cmdList)->nextcommand;
1001 setStart = (*cmdList);
1003 /* Skip until the close bracket */
1004 WINE_TRACE("Searching %p as the set\n", *cmdList);
1006 (*cmdList)->command != NULL &&
1007 (*cmdList)->bracketDepth > thisDepth) {
1008 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
1009 *cmdList = (*cmdList)->nextcommand;
1012 /* Skip the close bracket, if there is one */
1013 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
1015 /* Syntax error if missing close bracket, or nothing following it
1016 and once we have the complete set, we expect a DO */
1017 WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
1018 if ((*cmdList == NULL)
1019 || !WCMD_keyword_ws_found(doW, sizeof(doW)/sizeof(doW[0]), (*cmdList)->command)) {
1021 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1025 /* Save away the starting position for the commands (and offset for the
1027 cmdStart = *cmdList;
1029 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
1033 /* Loop through all set entries */
1035 thisSet->command != NULL &&
1036 thisSet->bracketDepth >= thisDepth) {
1038 /* Loop through all entries on the same line */
1042 WINE_TRACE("Processing for set %p\n", thisSet);
1044 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart, NULL))) {
1047 * If the parameter within the set has a wildcard then search for matching files
1048 * otherwise do a literal substitution.
1050 static const WCHAR wildcards[] = {'*','?','\0'};
1051 thisCmdStart = cmdStart;
1054 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
1056 if (!useNumbers && !doFileset) {
1057 if (strpbrkW (item, wildcards)) {
1058 hff = FindFirstFileW(item, &fd);
1059 if (hff != INVALID_HANDLE_VALUE) {
1061 BOOL isDirectory = FALSE;
1063 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
1065 /* Handle as files or dirs appropriately, but ignore . and .. */
1066 if (isDirectory == expandDirs &&
1067 (strcmpW(fd.cFileName, dotdotW) != 0) &&
1068 (strcmpW(fd.cFileName, dotW) != 0))
1070 thisCmdStart = cmdStart;
1071 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
1072 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
1073 fd.cFileName, FALSE, TRUE);
1076 } while (FindNextFileW(hff, &fd) != 0);
1080 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
1083 } else if (useNumbers) {
1084 /* Convert the first 3 numbers to signed longs and save */
1085 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
1086 /* else ignore them! */
1088 /* Filesets - either a list of files, or a command to run and parse the output */
1089 } else if (doFileset && *itemStart != '"') {
1092 WCHAR temp_file[MAX_PATH];
1094 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
1095 wine_dbgstr_w(item));
1097 /* If backquote or single quote, we need to launch that command
1098 and parse the results - use a temporary file */
1099 if (*itemStart == '`' || *itemStart == '\'') {
1101 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
1102 static const WCHAR redirOut[] = {'>','%','s','\0'};
1103 static const WCHAR cmdW[] = {'C','M','D','\0'};
1105 /* Remove trailing character */
1106 itemStart[strlenW(itemStart)-1] = 0x00;
1108 /* Get temp filename */
1109 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
1110 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
1112 /* Execute program and redirect output */
1113 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
1114 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
1116 /* Open the file, read line by line and process */
1117 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
1118 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1121 /* Open the file, read line by line and process */
1122 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
1123 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1126 /* Process the input file */
1127 if (input == INVALID_HANDLE_VALUE) {
1128 WCMD_print_error ();
1129 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
1131 return; /* FOR loop aborts at first failure here */
1135 WCHAR buffer[MAXSTRING] = {'\0'};
1136 WCHAR *where, *parm;
1138 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
1140 /* Skip blank lines*/
1141 parm = WCMD_parameter (buffer, 0, &where, NULL);
1142 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1143 wine_dbgstr_w(buffer));
1146 /* FIXME: The following should be moved into its own routine and
1147 reused for the string literal parsing below */
1148 thisCmdStart = cmdStart;
1149 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1150 cmdEnd = thisCmdStart;
1156 CloseHandle (input);
1159 /* Delete the temporary file */
1160 if (*itemStart == '`' || *itemStart == '\'') {
1161 DeleteFileW(temp_file);
1164 /* Filesets - A string literal */
1165 } else if (doFileset && *itemStart == '"') {
1166 WCHAR buffer[MAXSTRING] = {'\0'};
1167 WCHAR *where, *parm;
1169 /* Skip blank lines, and re-extract parameter now string has quotes removed */
1170 strcpyW(buffer, item);
1171 parm = WCMD_parameter (buffer, 0, &where, NULL);
1172 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
1173 wine_dbgstr_w(buffer));
1176 /* FIXME: The following should be moved into its own routine and
1177 reused for the string literal parsing below */
1178 thisCmdStart = cmdStart;
1179 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
1180 cmdEnd = thisCmdStart;
1184 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
1185 cmdEnd = thisCmdStart;
1189 /* Move onto the next set line */
1190 thisSet = thisSet->nextcommand;
1193 /* If /L is provided, now run the for loop */
1196 static const WCHAR fmt[] = {'%','d','\0'};
1198 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
1199 numbers[0], numbers[2], numbers[1]);
1201 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1204 sprintfW(thisNum, fmt, i);
1205 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1207 thisCmdStart = cmdStart;
1208 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1209 cmdEnd = thisCmdStart;
1213 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1214 all processing, OR it should be pointing to the end of && processing OR
1215 it should be pointing at the NULL end of bracket for the DO. The return
1216 value needs to be the NEXT command to execute, which it either is, or
1217 we need to step over the closing bracket */
1219 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1223 /*****************************************************************************
1226 * Execute a command, and any && or bracketed follow on to the command. The
1227 * first command to be executed may not be at the front of the
1228 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1230 static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
1231 const WCHAR *variable, const WCHAR *value,
1232 BOOL isIF, BOOL conditionTRUE) {
1234 CMD_LIST *curPosition = *cmdList;
1235 int myDepth = (*cmdList)->bracketDepth;
1237 WINE_TRACE("cmdList(%p), firstCmd(%p), with variable '%s'='%s', doIt(%d)\n",
1238 cmdList, wine_dbgstr_w(firstcmd),
1239 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1242 /* Skip leading whitespace between condition and the command */
1243 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1245 /* Process the first command, if there is one */
1246 if (conditionTRUE && firstcmd && *firstcmd) {
1247 WCHAR *command = WCMD_strdupW(firstcmd);
1248 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1249 HeapFree(GetProcessHeap(), 0, command);
1253 /* If it didn't move the position, step to next command */
1254 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1256 /* Process any other parts of the command */
1258 BOOL processThese = TRUE;
1260 if (isIF) processThese = conditionTRUE;
1263 static const WCHAR ifElse[] = {'e','l','s','e'};
1265 /* execute all appropriate commands */
1266 curPosition = *cmdList;
1268 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1270 (*cmdList)->prevDelim,
1271 (*cmdList)->bracketDepth, myDepth);
1273 /* Execute any statements appended to the line */
1274 /* FIXME: Only if previous call worked for && or failed for || */
1275 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1276 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1277 if (processThese && (*cmdList)->command) {
1278 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1281 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1283 /* Execute any appended to the statement with (...) */
1284 } else if ((*cmdList)->bracketDepth > myDepth) {
1286 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1287 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1289 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1291 /* End of the command - does 'ELSE ' follow as the next command? */
1294 && WCMD_keyword_ws_found(ifElse, sizeof(ifElse)/sizeof(ifElse[0]),
1295 (*cmdList)->command)) {
1297 /* Swap between if and else processing */
1298 processThese = !processThese;
1300 /* Process the ELSE part */
1302 const int keyw_len = sizeof(ifElse)/sizeof(ifElse[0]) + 1;
1303 WCHAR *cmd = ((*cmdList)->command) + keyw_len;
1305 /* Skip leading whitespace between condition and the command */
1306 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1308 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1311 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1313 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1322 /**************************************************************************
1325 * Simple on-line help. Help text is stored in the resource file.
1328 void WCMD_give_help (const WCHAR *command) {
1332 command = WCMD_skip_leading_spaces((WCHAR*) command);
1333 if (strlenW(command) == 0) {
1334 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1337 /* Display help message for builtin command */
1338 for (i=0; i<=WCMD_EXIT; i++) {
1339 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1340 command, -1, inbuilt[i], -1) == CSTR_EQUAL) {
1341 WCMD_output_asis (WCMD_LoadMessage(i));
1345 /* Launch the command with the /? option for external commands shipped with cmd.exe */
1346 for (i = 0; i <= (sizeof(externals)/sizeof(externals[0])); i++) {
1347 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1348 command, -1, externals[i], -1) == CSTR_EQUAL) {
1350 static const WCHAR helpW[] = {' ', '/','?','\0'};
1351 strcpyW(cmd, command);
1352 strcatW(cmd, helpW);
1353 WCMD_run_program(cmd, 0);
1357 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1362 /****************************************************************************
1365 * Batch file jump instruction. Not the most efficient algorithm ;-)
1366 * Prints error message if the specified label cannot be found - the file pointer is
1367 * then at EOF, effectively stopping the batch file.
1368 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1371 void WCMD_goto (CMD_LIST **cmdList) {
1373 WCHAR string[MAX_PATH];
1374 WCHAR current[MAX_PATH];
1376 /* Do not process any more parts of a processed multipart or multilines command */
1377 if (cmdList) *cmdList = NULL;
1379 if (param1[0] == 0x00) {
1380 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1383 if (context != NULL) {
1384 WCHAR *paramStart = param1, *str;
1385 static const WCHAR eofW[] = {':','e','o','f','\0'};
1387 /* Handle special :EOF label */
1388 if (lstrcmpiW (eofW, param1) == 0) {
1389 context -> skip_rest = TRUE;
1393 /* Support goto :label as well as goto label */
1394 if (*paramStart == ':') paramStart++;
1396 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1397 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1399 while (isspaceW (*str)) str++;
1403 while (((current[index] = str[index])) && (!isspaceW (current[index])))
1406 /* ignore space at the end */
1408 if (lstrcmpiW (current, paramStart) == 0) return;
1411 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1416 /*****************************************************************************
1419 * Push a directory onto the stack
1422 void WCMD_pushd (WCHAR *command) {
1423 struct env_stack *curdir;
1425 static const WCHAR parmD[] = {'/','D','\0'};
1427 if (strchrW(command, '/') != NULL) {
1428 SetLastError(ERROR_INVALID_PARAMETER);
1433 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1434 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1435 if( !curdir || !thisdir ) {
1438 WINE_ERR ("out of memory\n");
1442 /* Change directory using CD code with /D parameter */
1443 strcpyW(quals, parmD);
1444 GetCurrentDirectoryW (1024, thisdir);
1446 WCMD_setshow_default(command);
1452 curdir -> next = pushd_directories;
1453 curdir -> strings = thisdir;
1454 if (pushd_directories == NULL) {
1455 curdir -> u.stackdepth = 1;
1457 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1459 pushd_directories = curdir;
1464 /*****************************************************************************
1467 * Pop a directory from the stack
1470 void WCMD_popd (void) {
1471 struct env_stack *temp = pushd_directories;
1473 if (!pushd_directories)
1476 /* pop the old environment from the stack, and make it the current dir */
1477 pushd_directories = temp->next;
1478 SetCurrentDirectoryW(temp->strings);
1479 LocalFree (temp->strings);
1483 /****************************************************************************
1486 * Batch file conditional.
1488 * On entry, cmdlist will point to command containing the IF, and optionally
1489 * the first command to execute (if brackets not found)
1490 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1491 * If ('s were found, execute all within that bracket
1492 * Command may optionally be followed by an ELSE - need to skip instructions
1493 * in the else using the same logic
1495 * FIXME: Much more syntax checking needed!
1498 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1500 int negate; /* Negate condition */
1501 int test; /* Condition evaluation result */
1502 WCHAR condition[MAX_PATH], *command, *s;
1503 static const WCHAR notW[] = {'n','o','t','\0'};
1504 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1505 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1506 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1507 static const WCHAR eqeqW[] = {'=','=','\0'};
1508 static const WCHAR parmI[] = {'/','I','\0'};
1509 int caseInsensitive = (strstrW(quals, parmI) != NULL);
1511 negate = !lstrcmpiW(param1,notW);
1512 strcpyW(condition, (negate ? param2 : param1));
1513 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1515 if (!lstrcmpiW (condition, errlvlW)) {
1516 test = (errorlevel >= atoiW(WCMD_parameter(p, 1+negate, NULL, NULL)));
1517 WCMD_parameter(p, 2+negate, &command, NULL);
1519 else if (!lstrcmpiW (condition, existW)) {
1520 test = (GetFileAttributesW(WCMD_parameter(p, 1+negate, NULL, NULL)) != INVALID_FILE_ATTRIBUTES);
1521 WCMD_parameter(p, 2+negate, &command, NULL);
1523 else if (!lstrcmpiW (condition, defdW)) {
1524 test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+negate, NULL, NULL), NULL, 0) > 0);
1525 WCMD_parameter(p, 2+negate, &command, NULL);
1527 else if ((s = strstrW (p, eqeqW))) {
1529 test = caseInsensitive ? (!lstrcmpiW(condition, WCMD_parameter(s, 0, NULL, NULL)))
1530 : (!lstrcmpW (condition, WCMD_parameter(s, 0, NULL, NULL)));
1531 WCMD_parameter(s, 1, &command, NULL);
1534 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1538 /* Process rest of IF statement which is on the same line
1539 Note: This may process all or some of the cmdList (eg a GOTO) */
1540 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1543 /****************************************************************************
1546 * Move a file, directory tree or wildcarded set of files.
1549 void WCMD_move (void) {
1552 WIN32_FIND_DATAW fd;
1554 WCHAR input[MAX_PATH];
1555 WCHAR output[MAX_PATH];
1557 WCHAR dir[MAX_PATH];
1558 WCHAR fname[MAX_PATH];
1559 WCHAR ext[MAX_PATH];
1561 if (param1[0] == 0x00) {
1562 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1566 /* If no destination supplied, assume current directory */
1567 if (param2[0] == 0x00) {
1568 strcpyW(param2, dotW);
1571 /* If 2nd parm is directory, then use original filename */
1572 /* Convert partial path to full path */
1573 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1574 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1575 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1576 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1578 /* Split into components */
1579 WCMD_splitpath(input, drive, dir, fname, ext);
1581 hff = FindFirstFileW(input, &fd);
1582 while (hff != INVALID_HANDLE_VALUE) {
1583 WCHAR dest[MAX_PATH];
1584 WCHAR src[MAX_PATH];
1587 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1589 /* Build src & dest name */
1590 strcpyW(src, drive);
1593 /* See if dest is an existing directory */
1594 attribs = GetFileAttributesW(output);
1595 if (attribs != INVALID_FILE_ATTRIBUTES &&
1596 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1597 strcpyW(dest, output);
1598 strcatW(dest, slashW);
1599 strcatW(dest, fd.cFileName);
1601 strcpyW(dest, output);
1604 strcatW(src, fd.cFileName);
1606 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1607 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1609 /* Check if file is read only, otherwise move it */
1610 attribs = GetFileAttributesW(src);
1611 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1612 (attribs & FILE_ATTRIBUTE_READONLY)) {
1613 SetLastError(ERROR_ACCESS_DENIED);
1618 /* If destination exists, prompt unless /Y supplied */
1619 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1621 WCHAR copycmd[MAXSTRING];
1624 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1625 if (strstrW (quals, parmNoY))
1627 else if (strstrW (quals, parmY))
1630 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1631 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1632 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1633 && ! lstrcmpiW (copycmd, parmY));
1636 /* Prompt if overwriting */
1638 WCHAR question[MAXSTRING];
1641 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1643 /* Ask for confirmation */
1644 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1645 ok = WCMD_ask_confirm(question, FALSE, NULL);
1647 /* So delete the destination prior to the move */
1649 if (!DeleteFileW(dest)) {
1650 WCMD_print_error ();
1659 status = MoveFileW(src, dest);
1661 status = 1; /* Anything other than 0 to prevent error msg below */
1666 WCMD_print_error ();
1670 /* Step on to next match */
1671 if (FindNextFileW(hff, &fd) == 0) {
1673 hff = INVALID_HANDLE_VALUE;
1679 /****************************************************************************
1682 * Wait for keyboard input.
1685 void WCMD_pause (void) {
1690 WCMD_output (anykey);
1691 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1692 sizeof(string)/sizeof(WCHAR), &count, NULL);
1695 /****************************************************************************
1698 * Delete a directory.
1701 void WCMD_remove_dir (WCHAR *command) {
1704 int argsProcessed = 0;
1705 WCHAR *argN = command;
1706 static const WCHAR parmS[] = {'/','S','\0'};
1707 static const WCHAR parmQ[] = {'/','Q','\0'};
1709 /* Loop through all args */
1711 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
1712 if (argN && argN[0] != '/') {
1713 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1714 wine_dbgstr_w(quals));
1717 /* If subdirectory search not supplied, just try to remove
1718 and report error if it fails (eg if it contains a file) */
1719 if (strstrW (quals, parmS) == NULL) {
1720 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1722 /* Otherwise use ShFileOp to recursively remove a directory */
1725 SHFILEOPSTRUCTW lpDir;
1728 if (strstrW (quals, parmQ) == NULL) {
1730 WCHAR question[MAXSTRING];
1731 static const WCHAR fmt[] = {'%','s',' ','\0'};
1733 /* Ask for confirmation */
1734 wsprintfW(question, fmt, thisArg);
1735 ok = WCMD_ask_confirm(question, TRUE, NULL);
1737 /* Abort if answer is 'N' */
1744 lpDir.pFrom = thisArg;
1745 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1746 lpDir.wFunc = FO_DELETE;
1747 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1752 /* Handle no valid args */
1753 if (argsProcessed == 0) {
1754 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1760 /****************************************************************************
1766 void WCMD_rename (void) {
1770 WIN32_FIND_DATAW fd;
1771 WCHAR input[MAX_PATH];
1772 WCHAR *dotDst = NULL;
1774 WCHAR dir[MAX_PATH];
1775 WCHAR fname[MAX_PATH];
1776 WCHAR ext[MAX_PATH];
1781 /* Must be at least two args */
1782 if (param1[0] == 0x00 || param2[0] == 0x00) {
1783 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1788 /* Destination cannot contain a drive letter or directory separator */
1789 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1790 SetLastError(ERROR_INVALID_PARAMETER);
1796 /* Convert partial path to full path */
1797 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1798 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1799 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1800 dotDst = strchrW(param2, '.');
1802 /* Split into components */
1803 WCMD_splitpath(input, drive, dir, fname, ext);
1805 hff = FindFirstFileW(input, &fd);
1806 while (hff != INVALID_HANDLE_VALUE) {
1807 WCHAR dest[MAX_PATH];
1808 WCHAR src[MAX_PATH];
1809 WCHAR *dotSrc = NULL;
1812 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1814 /* FIXME: If dest name or extension is *, replace with filename/ext
1815 part otherwise use supplied name. This supports:
1817 ren jim.* fred.* etc
1818 However, windows has a more complex algorithm supporting eg
1819 ?'s and *'s mid name */
1820 dotSrc = strchrW(fd.cFileName, '.');
1822 /* Build src & dest name */
1823 strcpyW(src, drive);
1826 dirLen = strlenW(src);
1827 strcatW(src, fd.cFileName);
1830 if (param2[0] == '*') {
1831 strcatW(dest, fd.cFileName);
1832 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1834 strcatW(dest, param2);
1835 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1838 /* Build Extension */
1839 if (dotDst && (*(dotDst+1)=='*')) {
1840 if (dotSrc) strcatW(dest, dotSrc);
1841 } else if (dotDst) {
1842 if (dotDst) strcatW(dest, dotDst);
1845 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1846 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1848 /* Check if file is read only, otherwise move it */
1849 attribs = GetFileAttributesW(src);
1850 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1851 (attribs & FILE_ATTRIBUTE_READONLY)) {
1852 SetLastError(ERROR_ACCESS_DENIED);
1855 status = MoveFileW(src, dest);
1859 WCMD_print_error ();
1863 /* Step on to next match */
1864 if (FindNextFileW(hff, &fd) == 0) {
1866 hff = INVALID_HANDLE_VALUE;
1872 /*****************************************************************************
1875 * Make a copy of the environment.
1877 static WCHAR *WCMD_dupenv( const WCHAR *env )
1887 len += (strlenW(&env[len]) + 1);
1889 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1892 WINE_ERR("out of memory\n");
1895 memcpy (env_copy, env, len*sizeof (WCHAR));
1901 /*****************************************************************************
1904 * setlocal pushes the environment onto a stack
1905 * Save the environment as unicode so we don't screw anything up.
1907 void WCMD_setlocal (const WCHAR *s) {
1909 struct env_stack *env_copy;
1910 WCHAR cwd[MAX_PATH];
1912 /* DISABLEEXTENSIONS ignored */
1914 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1917 WINE_ERR ("out of memory\n");
1921 env = GetEnvironmentStringsW ();
1923 env_copy->strings = WCMD_dupenv (env);
1924 if (env_copy->strings)
1926 env_copy->next = saved_environment;
1927 saved_environment = env_copy;
1929 /* Save the current drive letter */
1930 GetCurrentDirectoryW(MAX_PATH, cwd);
1931 env_copy->u.cwd = cwd[0];
1934 LocalFree (env_copy);
1936 FreeEnvironmentStringsW (env);
1940 /*****************************************************************************
1943 * endlocal pops the environment off a stack
1944 * Note: When searching for '=', search from WCHAR position 1, to handle
1945 * special internal environment variables =C:, =D: etc
1947 void WCMD_endlocal (void) {
1948 WCHAR *env, *old, *p;
1949 struct env_stack *temp;
1952 if (!saved_environment)
1955 /* pop the old environment from the stack */
1956 temp = saved_environment;
1957 saved_environment = temp->next;
1959 /* delete the current environment, totally */
1960 env = GetEnvironmentStringsW ();
1961 old = WCMD_dupenv (GetEnvironmentStringsW ());
1964 n = strlenW(&old[len]) + 1;
1965 p = strchrW(&old[len] + 1, '=');
1969 SetEnvironmentVariableW (&old[len], NULL);
1974 FreeEnvironmentStringsW (env);
1976 /* restore old environment */
1977 env = temp->strings;
1980 n = strlenW(&env[len]) + 1;
1981 p = strchrW(&env[len] + 1, '=');
1985 SetEnvironmentVariableW (&env[len], p);
1990 /* Restore current drive letter */
1991 if (IsCharAlphaW(temp->u.cwd)) {
1993 WCHAR cwd[MAX_PATH];
1994 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1996 wsprintfW(envvar, fmt, temp->u.cwd);
1997 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1998 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1999 SetCurrentDirectoryW(cwd);
2007 /*****************************************************************************
2008 * WCMD_setshow_default
2010 * Set/Show the current default directory
2013 void WCMD_setshow_default (const WCHAR *command) {
2019 WIN32_FIND_DATAW fd;
2021 static const WCHAR parmD[] = {'/','D','\0'};
2023 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
2025 /* Skip /D and trailing whitespace if on the front of the command line */
2026 if (CompareStringW(LOCALE_USER_DEFAULT,
2027 NORM_IGNORECASE | SORT_STRINGSORT,
2028 command, 2, parmD, -1) == CSTR_EQUAL) {
2030 while (*command && (*command==' ' || *command=='\t'))
2034 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
2035 if (strlenW(command) == 0) {
2036 strcatW (cwd, newline);
2040 /* Remove any double quotes, which may be in the
2041 middle, eg. cd "C:\Program Files"\Microsoft is ok */
2044 if (*command != '"') *pos++ = *command;
2047 while (pos > command && (*(pos-1) == ' ' || *(pos-1) == '\t'))
2051 /* Search for appropriate directory */
2052 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
2053 hff = FindFirstFileW(string, &fd);
2054 while (hff != INVALID_HANDLE_VALUE) {
2055 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2056 WCHAR fpath[MAX_PATH];
2058 WCHAR dir[MAX_PATH];
2059 WCHAR fname[MAX_PATH];
2060 WCHAR ext[MAX_PATH];
2061 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
2063 /* Convert path into actual directory spec */
2064 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
2065 WCMD_splitpath(fpath, drive, dir, fname, ext);
2068 wsprintfW(string, fmt, drive, dir, fd.cFileName);
2071 hff = INVALID_HANDLE_VALUE;
2075 /* Step on to next match */
2076 if (FindNextFileW(hff, &fd) == 0) {
2078 hff = INVALID_HANDLE_VALUE;
2083 /* Change to that directory */
2084 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
2086 status = SetCurrentDirectoryW(string);
2089 WCMD_print_error ();
2093 /* Save away the actual new directory, to store as current location */
2094 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
2096 /* Restore old directory if drive letter would change, and
2097 CD x:\directory /D (or pushd c:\directory) not supplied */
2098 if ((strstrW(quals, parmD) == NULL) &&
2099 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
2100 SetCurrentDirectoryW(cwd);
2104 /* Set special =C: type environment variable, for drive letter of
2105 change of directory, even if path was restored due to missing
2106 /D (allows changing drive letter when not resident on that
2108 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
2110 strcpyW(env, equalW);
2111 memcpy(env+1, string, 2 * sizeof(WCHAR));
2113 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
2114 SetEnvironmentVariableW(env, string);
2121 /****************************************************************************
2124 * Set/Show the system date
2125 * FIXME: Can't change date yet
2128 void WCMD_setshow_date (void) {
2130 WCHAR curdate[64], buffer[64];
2132 static const WCHAR parmT[] = {'/','T','\0'};
2134 if (strlenW(param1) == 0) {
2135 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2136 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2137 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2138 if (strstrW (quals, parmT) == NULL) {
2139 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2140 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2141 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2143 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2147 else WCMD_print_error ();
2150 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2154 /****************************************************************************
2157 static int WCMD_compare( const void *a, const void *b )
2160 const WCHAR * const *str_a = a, * const *str_b = b;
2161 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2162 *str_a, -1, *str_b, -1 );
2163 if( r == CSTR_LESS_THAN ) return -1;
2164 if( r == CSTR_GREATER_THAN ) return 1;
2168 /****************************************************************************
2169 * WCMD_setshow_sortenv
2171 * sort variables into order for display
2172 * Optionally only display those who start with a stub
2173 * returns the count displayed
2175 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2177 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2180 if (stub) stublen = strlenW(stub);
2182 /* count the number of strings, and the total length */
2184 len += (strlenW(&s[len]) + 1);
2188 /* add the strings to an array */
2189 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2193 for( i=1; i<count; i++ )
2194 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2196 /* sort the array */
2197 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2200 for( i=0; i<count; i++ ) {
2201 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2202 NORM_IGNORECASE | SORT_STRINGSORT,
2203 str[i], stublen, stub, -1) == CSTR_EQUAL) {
2204 /* Don't display special internal variables */
2205 if (str[i][0] != '=') {
2206 WCMD_output_asis(str[i]);
2207 WCMD_output_asis(newline);
2214 return displayedcount;
2217 /****************************************************************************
2220 * Set/Show the environment variables
2223 void WCMD_setshow_env (WCHAR *s) {
2228 static const WCHAR parmP[] = {'/','P','\0'};
2230 if (param1[0] == 0x00 && quals[0] == 0x00) {
2231 env = GetEnvironmentStringsW();
2232 WCMD_setshow_sortenv( env, NULL );
2236 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2237 if (CompareStringW(LOCALE_USER_DEFAULT,
2238 NORM_IGNORECASE | SORT_STRINGSORT,
2239 s, 2, parmP, -1) == CSTR_EQUAL) {
2240 WCHAR string[MAXSTRING];
2244 while (*s && (*s==' ' || *s=='\t')) s++;
2246 WCMD_opt_s_strip_quotes(s);
2248 /* If no parameter, or no '=' sign, return an error */
2249 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2250 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2254 /* Output the prompt */
2256 if (strlenW(p) != 0) WCMD_output(p);
2258 /* Read the reply */
2259 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2260 sizeof(string)/sizeof(WCHAR), &count, NULL);
2262 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2263 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2264 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2265 wine_dbgstr_w(string));
2266 status = SetEnvironmentVariableW(s, string);
2273 WCMD_opt_s_strip_quotes(s);
2274 p = strchrW (s, '=');
2276 env = GetEnvironmentStringsW();
2277 if (WCMD_setshow_sortenv( env, s ) == 0) {
2278 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2285 if (strlenW(p) == 0) p = NULL;
2286 status = SetEnvironmentVariableW(s, p);
2287 gle = GetLastError();
2288 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2290 } else if ((!status)) WCMD_print_error();
2294 /****************************************************************************
2297 * Set/Show the path environment variable
2300 void WCMD_setshow_path (const WCHAR *command) {
2304 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2305 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2307 if (strlenW(param1) == 0) {
2308 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2310 WCMD_output_asis ( pathEqW);
2311 WCMD_output_asis ( string);
2312 WCMD_output_asis ( newline);
2315 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2319 if (*command == '=') command++; /* Skip leading '=' */
2320 status = SetEnvironmentVariableW(pathW, command);
2321 if (!status) WCMD_print_error();
2325 /****************************************************************************
2326 * WCMD_setshow_prompt
2328 * Set or show the command prompt.
2331 void WCMD_setshow_prompt (void) {
2334 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2336 if (strlenW(param1) == 0) {
2337 SetEnvironmentVariableW(promptW, NULL);
2341 while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
2342 if (strlenW(s) == 0) {
2343 SetEnvironmentVariableW(promptW, NULL);
2345 else SetEnvironmentVariableW(promptW, s);
2349 /****************************************************************************
2352 * Set/Show the system time
2353 * FIXME: Can't change time yet
2356 void WCMD_setshow_time (void) {
2358 WCHAR curtime[64], buffer[64];
2361 static const WCHAR parmT[] = {'/','T','\0'};
2363 if (strlenW(param1) == 0) {
2365 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2366 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2367 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2368 if (strstrW (quals, parmT) == NULL) {
2369 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2370 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2371 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2373 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2377 else WCMD_print_error ();
2380 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2384 /****************************************************************************
2387 * Shift batch parameters.
2388 * Optional /n says where to start shifting (n=0-8)
2391 void WCMD_shift (const WCHAR *command) {
2394 if (context != NULL) {
2395 WCHAR *pos = strchrW(command, '/');
2400 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2401 start = (*(pos+1) - '0');
2403 SetLastError(ERROR_INVALID_PARAMETER);
2408 WINE_TRACE("Shifting variables, starting at %d\n", start);
2409 for (i=start;i<=8;i++) {
2410 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2412 context -> shift_count[9] = context -> shift_count[9] + 1;
2417 /****************************************************************************
2420 * Set the console title
2422 void WCMD_title (const WCHAR *command) {
2423 SetConsoleTitleW(command);
2426 /****************************************************************************
2429 * Copy a file to standard output.
2432 void WCMD_type (WCHAR *command) {
2435 WCHAR *argN = command;
2436 BOOL writeHeaders = FALSE;
2438 if (param1[0] == 0x00) {
2439 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2443 if (param2[0] != 0x00) writeHeaders = TRUE;
2445 /* Loop through all args */
2448 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2456 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2457 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2458 FILE_ATTRIBUTE_NORMAL, NULL);
2459 if (h == INVALID_HANDLE_VALUE) {
2460 WCMD_print_error ();
2461 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2465 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2466 WCMD_output(fmt, thisArg);
2468 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2469 if (count == 0) break; /* ReadFile reports success on EOF! */
2471 WCMD_output_asis (buffer);
2478 /****************************************************************************
2481 * Output either a file or stdin to screen in pages
2484 void WCMD_more (WCHAR *command) {
2487 WCHAR *argN = command;
2489 WCHAR moreStrPage[100];
2492 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2493 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2494 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2495 ')',' ','-','-','\n','\0'};
2496 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2498 /* Prefix the NLS more with '-- ', then load the text */
2500 strcpyW(moreStr, moreStart);
2501 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2502 (sizeof(moreStr)/sizeof(WCHAR))-3);
2504 if (param1[0] == 0x00) {
2506 /* Wine implements pipes via temporary files, and hence stdin is
2507 effectively reading from the file. This means the prompts for
2508 more are satisfied by the next line from the input (file). To
2509 avoid this, ensure stdin is to the console */
2510 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2511 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2512 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2513 FILE_ATTRIBUTE_NORMAL, 0);
2514 WINE_TRACE("No parms - working probably in pipe mode\n");
2515 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2517 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2518 once you get in this bit unless due to a pipe, its going to end badly... */
2519 wsprintfW(moreStrPage, moreFmt, moreStr);
2521 WCMD_enter_paged_mode(moreStrPage);
2522 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2523 if (count == 0) break; /* ReadFile reports success on EOF! */
2525 WCMD_output_asis (buffer);
2527 WCMD_leave_paged_mode();
2529 /* Restore stdin to what it was */
2530 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2531 CloseHandle(hConIn);
2535 BOOL needsPause = FALSE;
2537 /* Loop through all args */
2538 WINE_TRACE("Parms supplied - working through each file\n");
2539 WCMD_enter_paged_mode(moreStrPage);
2542 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN, NULL);
2550 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2551 WCMD_leave_paged_mode();
2552 WCMD_output_asis(moreStrPage);
2553 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2554 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2555 WCMD_enter_paged_mode(moreStrPage);
2559 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2560 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2561 FILE_ATTRIBUTE_NORMAL, NULL);
2562 if (h == INVALID_HANDLE_VALUE) {
2563 WCMD_print_error ();
2564 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2568 ULONG64 fileLen = 0;
2569 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2571 /* Get the file size */
2572 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2573 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2576 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2577 if (count == 0) break; /* ReadFile reports success on EOF! */
2581 /* Update % count (would be used in WCMD_output_asis as prompt) */
2582 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2584 WCMD_output_asis (buffer);
2590 WCMD_leave_paged_mode();
2594 /****************************************************************************
2597 * Display verify flag.
2598 * FIXME: We don't actually do anything with the verify flag other than toggle
2602 void WCMD_verify (const WCHAR *command) {
2606 count = strlenW(command);
2608 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2609 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2612 if (lstrcmpiW(command, onW) == 0) {
2616 else if (lstrcmpiW(command, offW) == 0) {
2620 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2623 /****************************************************************************
2626 * Display version info.
2629 void WCMD_version (void) {
2631 WCMD_output (version_string);
2635 /****************************************************************************
2638 * Display volume info and/or set volume label. Returns 0 if error.
2641 int WCMD_volume (int mode, const WCHAR *path) {
2643 DWORD count, serial;
2644 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2647 if (strlenW(path) == 0) {
2648 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2650 WCMD_print_error ();
2653 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2654 &serial, NULL, NULL, NULL, 0);
2657 static const WCHAR fmt[] = {'%','s','\\','\0'};
2658 if ((path[1] != ':') || (strlenW(path) != 2)) {
2659 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2662 wsprintfW (curdir, fmt, path);
2663 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2668 WCMD_print_error ();
2671 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2672 curdir[0], label, HIWORD(serial), LOWORD(serial));
2674 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2675 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2676 sizeof(string)/sizeof(WCHAR), &count, NULL);
2678 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2679 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2681 if (strlenW(path) != 0) {
2682 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2685 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2691 /**************************************************************************
2694 * Exit either the process, or just this batch program
2698 void WCMD_exit (CMD_LIST **cmdList) {
2700 static const WCHAR parmB[] = {'/','B','\0'};
2701 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2703 if (context && lstrcmpiW(quals, parmB) == 0) {
2705 context -> skip_rest = TRUE;
2713 /*****************************************************************************
2716 * Lists or sets file associations (assoc = TRUE)
2717 * Lists or sets file types (assoc = FALSE)
2719 void WCMD_assoc (const WCHAR *command, BOOL assoc) {
2722 DWORD accessOptions = KEY_READ;
2724 LONG rc = ERROR_SUCCESS;
2725 WCHAR keyValue[MAXSTRING];
2726 DWORD valueLen = MAXSTRING;
2728 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2729 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2731 /* See if parameter includes '=' */
2733 newValue = strchrW(command, '=');
2734 if (newValue) accessOptions |= KEY_WRITE;
2736 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2737 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2738 accessOptions, &key) != ERROR_SUCCESS) {
2739 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2743 /* If no parameters then list all associations */
2744 if (*command == 0x00) {
2747 /* Enumerate all the keys */
2748 while (rc != ERROR_NO_MORE_ITEMS) {
2749 WCHAR keyName[MAXSTRING];
2752 /* Find the next value */
2753 nameLen = MAXSTRING;
2754 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2756 if (rc == ERROR_SUCCESS) {
2758 /* Only interested in extension ones if assoc, or others
2760 if ((keyName[0] == '.' && assoc) ||
2761 (!(keyName[0] == '.') && (!assoc)))
2763 WCHAR subkey[MAXSTRING];
2764 strcpyW(subkey, keyName);
2765 if (!assoc) strcatW(subkey, shOpCmdW);
2767 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2769 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2770 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2771 WCMD_output_asis(keyName);
2772 WCMD_output_asis(equalW);
2773 /* If no default value found, leave line empty after '=' */
2774 if (rc == ERROR_SUCCESS) {
2775 WCMD_output_asis(keyValue);
2777 WCMD_output_asis(newline);
2778 RegCloseKey(readKey);
2786 /* Parameter supplied - if no '=' on command line, its a query */
2787 if (newValue == NULL) {
2789 WCHAR subkey[MAXSTRING];
2791 /* Query terminates the parameter at the first space */
2792 strcpyW(keyValue, command);
2793 space = strchrW(keyValue, ' ');
2794 if (space) *space=0x00;
2796 /* Set up key name */
2797 strcpyW(subkey, keyValue);
2798 if (!assoc) strcatW(subkey, shOpCmdW);
2800 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2802 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2803 WCMD_output_asis(command);
2804 WCMD_output_asis(equalW);
2805 /* If no default value found, leave line empty after '=' */
2806 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2807 WCMD_output_asis(newline);
2808 RegCloseKey(readKey);
2811 WCHAR msgbuffer[MAXSTRING];
2812 WCHAR outbuffer[MAXSTRING];
2814 /* Load the translated 'File association not found' */
2816 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2818 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2820 wsprintfW(outbuffer, msgbuffer, keyValue);
2821 WCMD_output_asis_stderr(outbuffer);
2825 /* Not a query - its a set or clear of a value */
2828 WCHAR subkey[MAXSTRING];
2830 /* Get pointer to new value */
2834 /* Set up key name */
2835 strcpyW(subkey, command);
2836 if (!assoc) strcatW(subkey, shOpCmdW);
2838 /* If nothing after '=' then clear value - only valid for ASSOC */
2839 if (*newValue == 0x00) {
2841 if (assoc) rc = RegDeleteKeyW(key, command);
2842 if (assoc && rc == ERROR_SUCCESS) {
2843 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2845 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2850 WCHAR msgbuffer[MAXSTRING];
2851 WCHAR outbuffer[MAXSTRING];
2853 /* Load the translated 'File association not found' */
2855 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2856 sizeof(msgbuffer)/sizeof(WCHAR));
2858 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2859 sizeof(msgbuffer)/sizeof(WCHAR));
2861 wsprintfW(outbuffer, msgbuffer, keyValue);
2862 WCMD_output_asis_stderr(outbuffer);
2866 /* It really is a set value = contents */
2868 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2869 accessOptions, NULL, &readKey, NULL);
2870 if (rc == ERROR_SUCCESS) {
2871 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2873 sizeof(WCHAR) * (strlenW(newValue) + 1));
2874 RegCloseKey(readKey);
2877 if (rc != ERROR_SUCCESS) {
2881 WCMD_output_asis(command);
2882 WCMD_output_asis(equalW);
2883 WCMD_output_asis(newValue);
2884 WCMD_output_asis(newline);
2894 /****************************************************************************
2897 * Clear the terminal screen.
2900 void WCMD_color (void) {
2902 /* Emulate by filling the screen from the top left to bottom right with
2903 spaces, then moving the cursor to the top left afterwards */
2904 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2905 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2907 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2908 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2912 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2918 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2923 /* Convert the color hex digits */
2924 if (param1[0] == 0x00) {
2925 color = defaultColor;
2927 color = strtoulW(param1, NULL, 16);
2930 /* Fail if fg == bg color */
2931 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2936 /* Set the current screen contents and ensure all future writes
2937 remain this color */
2938 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2939 SetConsoleTextAttribute(hStdOut, color);