2 * CMD - Wine-compatible command line interface - built-in functions.
4 * Copyright (C) 1999 D A Pickles
5 * Copyright (C) 2007 J Edmeades
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 * On entry to each function, global variables quals, param1, param2 contain
25 * the qualifiers (uppercased and concatenated) and parameters entered, with
26 * environment-variable and batch parameter substitution already done.
31 * - No support for pipes, shell parameters
32 * - Lots of functionality missing from builtins
33 * - Messages etc need international support
36 #define WIN32_LEAN_AND_MEAN
40 #include "wine/debug.h"
42 WINE_DEFAULT_DEBUG_CHANNEL(cmd);
44 static void WCMD_part_execute(CMD_LIST **commands, WCHAR *firstcmd, WCHAR *variable,
45 WCHAR *value, BOOL isIF, BOOL conditionTRUE);
47 struct env_stack *saved_environment;
48 struct env_stack *pushd_directories;
50 extern HINSTANCE hinst;
51 extern WCHAR inbuilt[][10];
52 extern int echo_mode, verify_mode, defaultColor;
53 extern WCHAR quals[MAX_PATH], param1[MAX_PATH], param2[MAX_PATH];
54 extern BATCH_CONTEXT *context;
55 extern DWORD errorlevel;
57 static const WCHAR dotW[] = {'.','\0'};
58 static const WCHAR dotdotW[] = {'.','.','\0'};
59 static const WCHAR slashW[] = {'\\','\0'};
60 static const WCHAR starW[] = {'*','\0'};
61 static const WCHAR equalW[] = {'=','\0'};
62 static const WCHAR fslashW[] = {'/','\0'};
63 static const WCHAR onW[] = {'O','N','\0'};
64 static const WCHAR offW[] = {'O','F','F','\0'};
65 static const WCHAR parmY[] = {'/','Y','\0'};
66 static const WCHAR parmNoY[] = {'/','-','Y','\0'};
67 static const WCHAR nullW[] = {'\0'};
69 /**************************************************************************
72 * Issue a message and ask 'Are you sure (Y/N)', waiting on a valid
75 * Returns True if Y (or A) answer is selected
76 * If optionAll contains a pointer, ALL is allowed, and if answered
80 static BOOL WCMD_ask_confirm (WCHAR *message, BOOL showSureText, BOOL *optionAll) {
82 WCHAR msgbuffer[MAXSTRING];
83 WCHAR Ybuffer[MAXSTRING];
84 WCHAR Nbuffer[MAXSTRING];
85 WCHAR Abuffer[MAXSTRING];
86 WCHAR answer[MAX_PATH] = {'\0'};
89 /* Load the translated 'Are you sure', plus valid answers */
90 LoadStringW(hinst, WCMD_CONFIRM, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
91 LoadStringW(hinst, WCMD_YES, Ybuffer, sizeof(Ybuffer)/sizeof(WCHAR));
92 LoadStringW(hinst, WCMD_NO, Nbuffer, sizeof(Nbuffer)/sizeof(WCHAR));
93 LoadStringW(hinst, WCMD_ALL, Abuffer, sizeof(Abuffer)/sizeof(WCHAR));
95 /* Loop waiting on a Y or N */
96 while (answer[0] != Ybuffer[0] && answer[0] != Nbuffer[0]) {
97 static const WCHAR startBkt[] = {' ','(','\0'};
98 static const WCHAR endBkt[] = {')','?','\0'};
100 WCMD_output_asis (message);
102 WCMD_output_asis (msgbuffer);
104 WCMD_output_asis (startBkt);
105 WCMD_output_asis (Ybuffer);
106 WCMD_output_asis (fslashW);
107 WCMD_output_asis (Nbuffer);
109 WCMD_output_asis (fslashW);
110 WCMD_output_asis (Abuffer);
112 WCMD_output_asis (endBkt);
113 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), answer,
114 sizeof(answer)/sizeof(WCHAR), &count, NULL);
115 answer[0] = toupperW(answer[0]);
118 /* Return the answer */
119 return ((answer[0] == Ybuffer[0]) ||
120 (optionAll && (answer[0] == Abuffer[0])));
123 /****************************************************************************
126 * Clear the terminal screen.
129 void WCMD_clear_screen (void) {
131 /* Emulate by filling the screen from the top left to bottom right with
132 spaces, then moving the cursor to the top left afterwards */
133 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
134 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
136 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
141 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
145 FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &screenSize);
146 SetConsoleCursorPosition(hStdOut, topLeft);
150 /****************************************************************************
153 * Change the default i/o device (ie redirect STDin/STDout).
156 void WCMD_change_tty (void) {
158 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
162 /****************************************************************************
165 * Copy a file or wildcarded set.
166 * FIXME: Add support for a+b+c type syntax
169 void WCMD_copy (void) {
174 WCHAR outpath[MAX_PATH], srcpath[MAX_PATH], copycmd[4];
176 static const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
177 BOOL copyToDir = FALSE;
178 BOOL copyFromDir = FALSE;
179 WCHAR srcspec[MAX_PATH];
183 WCHAR fname[MAX_PATH];
186 if (param1[0] == 0x00) {
187 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
191 /* Convert source into full spec */
192 WINE_TRACE("Copy source (supplied): '%s'\n", wine_dbgstr_w(param1));
193 GetFullPathNameW(param1, sizeof(srcpath)/sizeof(WCHAR), srcpath, NULL);
194 if (srcpath[strlenW(srcpath) - 1] == '\\')
195 srcpath[strlenW(srcpath) - 1] = '\0';
197 if ((strchrW(srcpath,'*') == NULL) && (strchrW(srcpath,'?') == NULL)) {
198 attribs = GetFileAttributesW(srcpath);
202 strcpyW(srcspec, srcpath);
204 /* If a directory, then add \* on the end when searching */
205 if (attribs & FILE_ATTRIBUTE_DIRECTORY) {
206 strcatW(srcpath, slashW);
208 strcatW(srcspec, slashW);
209 strcatW(srcspec, starW);
211 WCMD_splitpath(srcpath, drive, dir, fname, ext);
212 strcpyW(srcpath, drive);
213 strcatW(srcpath, dir);
216 WINE_TRACE("Copy source (calculated): path: '%s'\n", wine_dbgstr_w(srcpath));
218 /* If no destination supplied, assume current directory */
219 WINE_TRACE("Copy destination (supplied): '%s'\n", wine_dbgstr_w(param2));
220 if (param2[0] == 0x00) {
221 strcpyW(param2, dotW);
224 GetFullPathNameW(param2, sizeof(outpath)/sizeof(WCHAR), outpath, NULL);
225 if (outpath[strlenW(outpath) - 1] == '\\')
226 outpath[strlenW(outpath) - 1] = '\0';
227 attribs = GetFileAttributesW(outpath);
228 if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
229 strcatW (outpath, slashW);
232 WINE_TRACE("Copy destination (calculated): '%s'(%d)\n",
233 wine_dbgstr_w(outpath), copyToDir);
235 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
236 if (strstrW (quals, parmNoY))
238 else if (strstrW (quals, parmY))
241 /* By default, we will force the overwrite in batch mode and ask for
242 * confirmation in interactive mode. */
245 /* If COPYCMD is set, then we force the overwrite with /Y and ask for
246 * confirmation with /-Y. If COPYCMD is neither of those, then we use the
247 * default behavior. */
248 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
249 if (len && len < (sizeof(copycmd)/sizeof(WCHAR))) {
250 if (!lstrcmpiW (copycmd, parmY))
252 else if (!lstrcmpiW (copycmd, parmNoY))
257 /* Loop through all source files */
258 WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcspec));
259 hff = FindFirstFileW(srcspec, &fd);
260 if (hff != INVALID_HANDLE_VALUE) {
262 WCHAR outname[MAX_PATH];
263 WCHAR srcname[MAX_PATH];
264 BOOL overwrite = force;
266 /* Destination is either supplied filename, or source name in
267 supplied destination directory */
268 strcpyW(outname, outpath);
269 if (copyToDir) strcatW(outname, fd.cFileName);
270 strcpyW(srcname, srcpath);
271 strcatW(srcname, fd.cFileName);
273 WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcname));
274 WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
276 /* Skip . and .., and directories */
277 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
279 WINE_TRACE("Skipping directories\n");
282 /* Prompt before overwriting */
283 else if (!overwrite) {
284 attribs = GetFileAttributesW(outname);
285 if (attribs != INVALID_FILE_ATTRIBUTES) {
286 WCHAR buffer[MAXSTRING];
287 wsprintfW(buffer, WCMD_LoadMessage(WCMD_OVERWRITE), outname);
288 overwrite = WCMD_ask_confirm(buffer, FALSE, NULL);
290 else overwrite = TRUE;
293 /* Do the copy as appropriate */
295 status = CopyFileW(srcname, outname, FALSE);
296 if (!status) WCMD_print_error ();
299 } while (FindNextFileW(hff, &fd) != 0);
302 status = ERROR_FILE_NOT_FOUND;
307 /****************************************************************************
310 * Create a directory.
312 * this works recursively. so mkdir dir1\dir2\dir3 will create dir1 and dir2 if
313 * they do not already exist.
316 static BOOL create_full_path(WCHAR* path)
322 new_path = HeapAlloc(GetProcessHeap(),0,(strlenW(path)+1) * sizeof(WCHAR));
323 strcpyW(new_path,path);
325 while ((len = strlenW(new_path)) && new_path[len - 1] == '\\')
326 new_path[len - 1] = 0;
328 while (!CreateDirectoryW(new_path,NULL))
331 DWORD last_error = GetLastError();
332 if (last_error == ERROR_ALREADY_EXISTS)
335 if (last_error != ERROR_PATH_NOT_FOUND)
341 if (!(slash = strrchrW(new_path,'\\')) && ! (slash = strrchrW(new_path,'/')))
347 len = slash - new_path;
349 if (!create_full_path(new_path))
354 new_path[len] = '\\';
356 HeapFree(GetProcessHeap(),0,new_path);
360 void WCMD_create_dir (void) {
362 if (param1[0] == 0x00) {
363 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
366 if (!create_full_path(param1)) WCMD_print_error ();
369 /****************************************************************************
372 * Delete a file or wildcarded set.
375 * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
376 * - Each set is a pattern, eg /ahr /as-r means
377 * readonly+hidden OR nonreadonly system files
378 * - The '-' applies to a single field, ie /a:-hr means read only
382 BOOL WCMD_delete (WCHAR *command, BOOL expectDir) {
385 int argsProcessed = 0;
386 WCHAR *argN = command;
387 BOOL foundAny = FALSE;
388 static const WCHAR parmA[] = {'/','A','\0'};
389 static const WCHAR parmQ[] = {'/','Q','\0'};
390 static const WCHAR parmP[] = {'/','P','\0'};
391 static const WCHAR parmS[] = {'/','S','\0'};
392 static const WCHAR parmF[] = {'/','F','\0'};
394 /* If not recursing, clear error flag */
395 if (expectDir) errorlevel = 0;
397 /* Loop through all args */
399 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
400 WCHAR argCopy[MAX_PATH];
402 if (argN && argN[0] != '/') {
406 WCHAR fpath[MAX_PATH];
408 BOOL handleParm = TRUE;
410 static const WCHAR anyExt[]= {'.','*','\0'};
412 strcpyW(argCopy, thisArg);
413 WINE_TRACE("del: Processing arg %s (quals:%s)\n",
414 wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
417 /* If filename part of parameter is * or *.*, prompt unless
419 if ((strstrW (quals, parmQ) == NULL) && (strstrW (quals, parmP) == NULL)) {
423 WCHAR fname[MAX_PATH];
426 /* Convert path into actual directory spec */
427 GetFullPathNameW(argCopy, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
428 WCMD_splitpath(fpath, drive, dir, fname, ext);
430 /* Only prompt for * and *.*, not *a, a*, *.a* etc */
431 if ((strcmpW(fname, starW) == 0) &&
432 (*ext == 0x00 || (strcmpW(ext, anyExt) == 0))) {
434 WCHAR question[MAXSTRING];
435 static const WCHAR fmt[] = {'%','s',' ','\0'};
437 /* Note: Flag as found, to avoid file not found message */
440 /* Ask for confirmation */
441 wsprintfW(question, fmt, fpath);
442 ok = WCMD_ask_confirm(question, TRUE, NULL);
444 /* Abort if answer is 'N' */
449 /* First, try to delete in the current directory */
450 hff = FindFirstFileW(argCopy, &fd);
451 if (hff == INVALID_HANDLE_VALUE) {
457 /* Support del <dirname> by just deleting all files dirname\* */
458 if (handleParm && (strchrW(argCopy,'*') == NULL) && (strchrW(argCopy,'?') == NULL)
459 && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
460 WCHAR modifiedParm[MAX_PATH];
461 static const WCHAR slashStar[] = {'\\','*','\0'};
463 strcpyW(modifiedParm, argCopy);
464 strcatW(modifiedParm, slashStar);
467 WCMD_delete(modifiedParm, FALSE);
469 } else if (handleParm) {
471 /* Build the filename to delete as <supplied directory>\<findfirst filename> */
472 strcpyW (fpath, argCopy);
474 p = strrchrW (fpath, '\\');
477 strcatW (fpath, fd.cFileName);
479 else strcpyW (fpath, fd.cFileName);
480 if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
482 WCHAR *nextA = strstrW (quals, parmA);
484 /* Handle attribute matching (/A) */
487 while (nextA != NULL && !ok) {
489 WCHAR *thisA = (nextA+2);
492 /* Skip optional : */
493 if (*thisA == ':') thisA++;
495 /* Parse each of the /A[:]xxx in turn */
496 while (*thisA && *thisA != '/') {
498 BOOL attribute = FALSE;
500 /* Match negation of attribute first */
506 /* Match attribute */
508 case 'R': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY);
510 case 'H': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
512 case 'S': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM);
514 case 'A': attribute = (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE);
517 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
520 /* Now check result, keeping a running boolean about whether it
521 matches all parsed attributes so far */
522 if (attribute && !negate) {
524 } else if (!attribute && negate) {
532 /* Save the running total as the final result */
535 /* Step on to next /A set */
536 nextA = strstrW (nextA+1, parmA);
540 /* /P means prompt for each file */
541 if (ok && strstrW (quals, parmP) != NULL) {
542 WCHAR question[MAXSTRING];
544 /* Ask for confirmation */
545 wsprintfW(question, WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
546 ok = WCMD_ask_confirm(question, FALSE, NULL);
549 /* Only proceed if ok to */
552 /* If file is read only, and /F supplied, delete it */
553 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
554 strstrW (quals, parmF) != NULL) {
555 SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
558 /* Now do the delete */
559 if (!DeleteFileW(fpath)) WCMD_print_error ();
563 } while (FindNextFileW(hff, &fd) != 0);
567 /* Now recurse into all subdirectories handling the parameter in the same way */
568 if (strstrW (quals, parmS) != NULL) {
570 WCHAR thisDir[MAX_PATH];
575 WCHAR fname[MAX_PATH];
578 /* Convert path into actual directory spec */
579 GetFullPathNameW(argCopy, sizeof(thisDir)/sizeof(WCHAR), thisDir, NULL);
580 WCMD_splitpath(thisDir, drive, dir, fname, ext);
582 strcpyW(thisDir, drive);
583 strcatW(thisDir, dir);
584 cPos = strlenW(thisDir);
586 WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
588 /* Append '*' to the directory */
590 thisDir[cPos+1] = 0x00;
592 hff = FindFirstFileW(thisDir, &fd);
594 /* Remove residual '*' */
595 thisDir[cPos] = 0x00;
597 if (hff != INVALID_HANDLE_VALUE) {
598 DIRECTORY_STACK *allDirs = NULL;
599 DIRECTORY_STACK *lastEntry = NULL;
602 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
603 (strcmpW(fd.cFileName, dotdotW) != 0) &&
604 (strcmpW(fd.cFileName, dotW) != 0)) {
606 DIRECTORY_STACK *nextDir;
607 WCHAR subParm[MAX_PATH];
609 /* Work out search parameter in sub dir */
610 strcpyW (subParm, thisDir);
611 strcatW (subParm, fd.cFileName);
612 strcatW (subParm, slashW);
613 strcatW (subParm, fname);
614 strcatW (subParm, ext);
615 WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
617 /* Allocate memory, add to list */
618 nextDir = HeapAlloc(GetProcessHeap(),0,sizeof(DIRECTORY_STACK));
619 if (allDirs == NULL) allDirs = nextDir;
620 if (lastEntry != NULL) lastEntry->next = nextDir;
622 nextDir->next = NULL;
623 nextDir->dirName = HeapAlloc(GetProcessHeap(),0,
624 (strlenW(subParm)+1) * sizeof(WCHAR));
625 strcpyW(nextDir->dirName, subParm);
627 } while (FindNextFileW(hff, &fd) != 0);
630 /* Go through each subdir doing the delete */
631 while (allDirs != NULL) {
632 DIRECTORY_STACK *tempDir;
634 tempDir = allDirs->next;
635 found |= WCMD_delete (allDirs->dirName, FALSE);
637 HeapFree(GetProcessHeap(),0,allDirs->dirName);
638 HeapFree(GetProcessHeap(),0,allDirs);
643 /* Keep running total to see if any found, and if not recursing
644 issue error message */
648 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), argCopy);
655 /* Handle no valid args */
656 if (argsProcessed == 0) {
657 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
663 /****************************************************************************
666 * Echo input to the screen (or not). We don't try to emulate the bugs
667 * in DOS (try typing "ECHO ON AGAIN" for an example).
670 void WCMD_echo (const WCHAR *command) {
674 if ((command[0] == '.') && (command[1] == 0)) {
675 WCMD_output (newline);
680 count = strlenW(command);
682 if (echo_mode) WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), onW);
683 else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), offW);
686 if (lstrcmpiW(command, onW) == 0) {
690 if (lstrcmpiW(command, offW) == 0) {
694 WCMD_output_asis (command);
695 WCMD_output (newline);
699 /**************************************************************************
702 * Batch file loop processing.
704 * On entry: cmdList contains the syntax up to the set
705 * next cmdList and all in that bracket contain the set data
706 * next cmdlist contains the DO cmd
707 * following that is either brackets or && entries (as per if)
711 void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
716 const WCHAR inW[] = {'i', 'n', ' ', '\0'};
717 const WCHAR doW[] = {'d', 'o', ' ', '\0'};
718 CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
724 BOOL expandDirs = FALSE;
725 BOOL useNumbers = FALSE;
726 BOOL doFileset = FALSE;
727 LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
729 CMD_LIST *thisCmdStart;
732 /* Handle optional qualifiers (multiple are allowed) */
733 while (*curPos && *curPos == '/') {
734 WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(curPos));
736 switch (toupperW(*curPos)) {
737 case 'D': curPos++; expandDirs = TRUE; break;
738 case 'L': curPos++; useNumbers = TRUE; break;
740 /* Recursive is special case - /R can have an optional path following it */
741 /* filenamesets are another special case - /F can have an optional options following it */
745 BOOL isRecursive = (*curPos == 'R');
750 /* Skip whitespace */
752 while (*curPos && *curPos==' ') curPos++;
754 /* Next parm is either qualifier, path/options or variable -
755 only care about it if it is the path/options */
756 if (*curPos && *curPos != '/' && *curPos != '%') {
757 if (isRecursive) WINE_FIXME("/R needs to handle supplied root\n");
758 else WINE_FIXME("/F needs to handle options\n");
763 WINE_FIXME("for qualifier '%c' unhandled\n", *curPos);
767 /* Skip whitespace between qualifiers */
768 while (*curPos && *curPos==' ') curPos++;
771 /* Skip whitespace before variable */
772 while (*curPos && *curPos==' ') curPos++;
774 /* Ensure line continues with variable */
775 if (!*curPos || *curPos != '%') {
776 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
780 /* Variable should follow */
782 while (curPos[i] && curPos[i]!=' ') i++;
783 memcpy(&variable[0], curPos, i*sizeof(WCHAR));
785 WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
788 /* Skip whitespace before IN */
789 while (*curPos && *curPos==' ') curPos++;
791 /* Ensure line continues with IN */
792 if (!*curPos || lstrcmpiW (curPos, inW)) {
793 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
797 /* Save away where the set of data starts and the variable */
798 thisDepth = (*cmdList)->bracketDepth;
799 *cmdList = (*cmdList)->nextcommand;
800 setStart = (*cmdList);
802 /* Skip until the close bracket */
803 WINE_TRACE("Searching %p as the set\n", *cmdList);
805 (*cmdList)->command != NULL &&
806 (*cmdList)->bracketDepth > thisDepth) {
807 WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
808 *cmdList = (*cmdList)->nextcommand;
811 /* Skip the close bracket, if there is one */
812 if (*cmdList) *cmdList = (*cmdList)->nextcommand;
814 /* Syntax error if missing close bracket, or nothing following it
815 and once we have the complete set, we expect a DO */
816 WINE_TRACE("Looking for 'do' in %p\n", *cmdList);
817 if ((*cmdList == NULL) ||
818 (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
819 (*cmdList)->command, 3, doW, -1) != 2)) {
820 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
824 /* Save away the starting position for the commands (and offset for the
828 firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
832 /* Loop through all set entries */
834 thisSet->command != NULL &&
835 thisSet->bracketDepth >= thisDepth) {
837 /* Loop through all entries on the same line */
841 WINE_TRACE("Processing for set %p\n", thisSet);
843 while (*(item = WCMD_parameter (thisSet->command, i, &itemStart))) {
846 * If the parameter within the set has a wildcard then search for matching files
847 * otherwise do a literal substitution.
849 static const WCHAR wildcards[] = {'*','?','\0'};
850 thisCmdStart = cmdStart;
853 WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
855 if (!useNumbers && !doFileset) {
856 if (strpbrkW (item, wildcards)) {
857 hff = FindFirstFileW(item, &fd);
858 if (hff != INVALID_HANDLE_VALUE) {
860 BOOL isDirectory = FALSE;
862 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
864 /* Handle as files or dirs appropriately, but ignore . and .. */
865 if (isDirectory == expandDirs &&
866 (strcmpW(fd.cFileName, dotdotW) != 0) &&
867 (strcmpW(fd.cFileName, dotW) != 0))
869 thisCmdStart = cmdStart;
870 WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
871 WCMD_part_execute (&thisCmdStart, firstCmd, variable,
872 fd.cFileName, FALSE, TRUE);
875 } while (FindNextFileW(hff, &fd) != 0);
879 WCMD_part_execute(&thisCmdStart, firstCmd, variable, item, FALSE, TRUE);
882 } else if (useNumbers) {
883 /* Convert the first 3 numbers to signed longs and save */
884 if (itemNum <=3) numbers[itemNum-1] = atolW(item);
885 /* else ignore them! */
887 /* Filesets - either a list of files, or a command to run and parse the output */
888 } else if (doFileset && *itemStart != '"') {
891 WCHAR temp_file[MAX_PATH];
893 WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
894 wine_dbgstr_w(item));
896 /* If backquote or single quote, we need to launch that command
897 and parse the results - use a temporary file */
898 if (*itemStart == '`' || *itemStart == '\'') {
900 WCHAR temp_path[MAX_PATH], temp_cmd[MAXSTRING];
901 static const WCHAR redirOut[] = {'>','%','s','\0'};
902 static const WCHAR cmdW[] = {'C','M','D','\0'};
904 /* Remove trailing character */
905 itemStart[strlenW(itemStart)-1] = 0x00;
907 /* Get temp filename */
908 GetTempPathW(sizeof(temp_path)/sizeof(WCHAR), temp_path);
909 GetTempFileNameW(temp_path, cmdW, 0, temp_file);
911 /* Execute program and redirect output */
912 wsprintfW(temp_cmd, redirOut, (itemStart+1), temp_file);
913 WCMD_execute (itemStart, temp_cmd, NULL, NULL, NULL);
915 /* Open the file, read line by line and process */
916 input = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
917 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
920 /* Open the file, read line by line and process */
921 input = CreateFileW(item, GENERIC_READ, FILE_SHARE_READ,
922 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
925 /* Process the input file */
926 if (input == INVALID_HANDLE_VALUE) {
928 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), item);
930 return; /* FOR loop aborts at first failure here */
934 WCHAR buffer[MAXSTRING] = {'\0'};
937 while (WCMD_fgets (buffer, sizeof(buffer)/sizeof(WCHAR), input)) {
939 /* Skip blank lines*/
940 parm = WCMD_parameter (buffer, 0, &where);
941 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
942 wine_dbgstr_w(buffer));
945 /* FIXME: The following should be moved into its own routine and
946 reused for the string literal parsing below */
947 thisCmdStart = cmdStart;
948 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
949 cmdEnd = thisCmdStart;
958 /* Delete the temporary file */
959 if (*itemStart == '`' || *itemStart == '\'') {
960 DeleteFileW(temp_file);
963 /* Filesets - A string literal */
964 } else if (doFileset && *itemStart == '"') {
965 WCHAR buffer[MAXSTRING] = {'\0'};
968 /* Skip blank lines, and re-extract parameter now string has quotes removed */
969 strcpyW(buffer, item);
970 parm = WCMD_parameter (buffer, 0, &where);
971 WINE_TRACE("Parsed parameter: %s from %s\n", wine_dbgstr_w(parm),
972 wine_dbgstr_w(buffer));
975 /* FIXME: The following should be moved into its own routine and
976 reused for the string literal parsing below */
977 thisCmdStart = cmdStart;
978 WCMD_part_execute(&thisCmdStart, firstCmd, variable, parm, FALSE, TRUE);
979 cmdEnd = thisCmdStart;
983 WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
984 cmdEnd = thisCmdStart;
988 /* Move onto the next set line */
989 thisSet = thisSet->nextcommand;
992 /* If /L is provided, now run the for loop */
995 static const WCHAR fmt[] = {'%','d','\0'};
997 WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
998 numbers[0], numbers[2], numbers[1]);
1000 (numbers[1]<0)? i>numbers[2] : i<numbers[2];
1003 sprintfW(thisNum, fmt, i);
1004 WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
1006 thisCmdStart = cmdStart;
1007 WCMD_part_execute(&thisCmdStart, firstCmd, variable, thisNum, FALSE, TRUE);
1008 cmdEnd = thisCmdStart;
1012 /* When the loop ends, either something like a GOTO or EXIT /b has terminated
1013 all processing, OR it should be pointing to the end of && processing OR
1014 it should be pointing at the NULL end of bracket for the DO. The return
1015 value needs to be the NEXT command to execute, which it either is, or
1016 we need to step over the closing bracket */
1018 if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
1022 /*****************************************************************************
1025 * Execute a command, and any && or bracketed follow on to the command. The
1026 * first command to be executed may not be at the front of the
1027 * commands->thiscommand string (eg. it may point after a DO or ELSE)
1029 void WCMD_part_execute(CMD_LIST **cmdList, WCHAR *firstcmd, WCHAR *variable,
1030 WCHAR *value, BOOL isIF, BOOL conditionTRUE) {
1032 CMD_LIST *curPosition = *cmdList;
1033 int myDepth = (*cmdList)->bracketDepth;
1035 WINE_TRACE("cmdList(%p), firstCmd(%p), with '%s'='%s', doIt(%d)\n",
1036 cmdList, wine_dbgstr_w(firstcmd),
1037 wine_dbgstr_w(variable), wine_dbgstr_w(value),
1040 /* Skip leading whitespace between condition and the command */
1041 while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
1043 /* Process the first command, if there is one */
1044 if (conditionTRUE && firstcmd && *firstcmd) {
1045 WCHAR *command = WCMD_strdupW(firstcmd);
1046 WCMD_execute (firstcmd, (*cmdList)->redirects, variable, value, cmdList);
1047 HeapFree(GetProcessHeap(), 0, command);
1051 /* If it didn't move the position, step to next command */
1052 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1054 /* Process any other parts of the command */
1056 BOOL processThese = TRUE;
1058 if (isIF) processThese = conditionTRUE;
1061 const WCHAR ifElse[] = {'e','l','s','e',' ','\0'};
1063 /* execute all appropriate commands */
1064 curPosition = *cmdList;
1066 WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d)\n",
1068 (*cmdList)->prevDelim,
1069 (*cmdList)->bracketDepth, myDepth);
1071 /* Execute any statements appended to the line */
1072 /* FIXME: Only if previous call worked for && or failed for || */
1073 if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
1074 (*cmdList)->prevDelim == CMD_ONSUCCESS) {
1075 if (processThese && (*cmdList)->command) {
1076 WCMD_execute ((*cmdList)->command, (*cmdList)->redirects, variable,
1079 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1081 /* Execute any appended to the statement with (...) */
1082 } else if ((*cmdList)->bracketDepth > myDepth) {
1084 *cmdList = WCMD_process_commands(*cmdList, TRUE, variable, value);
1085 WINE_TRACE("Back from processing commands, (next = %p)\n", *cmdList);
1087 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1089 /* End of the command - does 'ELSE ' follow as the next command? */
1091 if (isIF && CompareStringW(LOCALE_USER_DEFAULT,
1092 NORM_IGNORECASE | SORT_STRINGSORT,
1093 (*cmdList)->command, 5, ifElse, -1) == 2) {
1095 /* Swap between if and else processing */
1096 processThese = !processThese;
1098 /* Process the ELSE part */
1100 WCHAR *cmd = ((*cmdList)->command) + strlenW(ifElse);
1102 /* Skip leading whitespace between condition and the command */
1103 while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
1105 WCMD_execute (cmd, (*cmdList)->redirects, variable, value, cmdList);
1108 if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
1110 WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
1119 /**************************************************************************
1122 * Simple on-line help. Help text is stored in the resource file.
1125 void WCMD_give_help (WCHAR *command) {
1129 command = WCMD_strtrim_leading_spaces(command);
1130 if (strlenW(command) == 0) {
1131 WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
1134 for (i=0; i<=WCMD_EXIT; i++) {
1135 if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
1136 command, -1, inbuilt[i], -1) == 2) {
1137 WCMD_output_asis (WCMD_LoadMessage(i));
1141 WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), command);
1146 /****************************************************************************
1149 * Batch file jump instruction. Not the most efficient algorithm ;-)
1150 * Prints error message if the specified label cannot be found - the file pointer is
1151 * then at EOF, effectively stopping the batch file.
1152 * FIXME: DOS is supposed to allow labels with spaces - we don't.
1155 void WCMD_goto (CMD_LIST **cmdList) {
1157 WCHAR string[MAX_PATH];
1159 /* Do not process any more parts of a processed multipart or multilines command */
1160 if (cmdList) *cmdList = NULL;
1162 if (param1[0] == 0x00) {
1163 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1166 if (context != NULL) {
1167 WCHAR *paramStart = param1, *str;
1168 static const WCHAR eofW[] = {':','e','o','f','\0'};
1170 /* Handle special :EOF label */
1171 if (lstrcmpiW (eofW, param1) == 0) {
1172 context -> skip_rest = TRUE;
1176 /* Support goto :label as well as goto label */
1177 if (*paramStart == ':') paramStart++;
1179 SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
1180 while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
1182 while (isspaceW(*str)) str++;
1183 if ((*str == ':') && (lstrcmpiW (++str, paramStart) == 0)) return;
1185 WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
1190 /*****************************************************************************
1193 * Push a directory onto the stack
1196 void WCMD_pushd (WCHAR *command) {
1197 struct env_stack *curdir;
1199 static const WCHAR parmD[] = {'/','D','\0'};
1201 if (strchrW(command, '/') != NULL) {
1202 SetLastError(ERROR_INVALID_PARAMETER);
1207 curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1208 thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
1209 if( !curdir || !thisdir ) {
1212 WINE_ERR ("out of memory\n");
1216 /* Change directory using CD code with /D parameter */
1217 strcpyW(quals, parmD);
1218 GetCurrentDirectoryW (1024, thisdir);
1220 WCMD_setshow_default(command);
1226 curdir -> next = pushd_directories;
1227 curdir -> strings = thisdir;
1228 if (pushd_directories == NULL) {
1229 curdir -> u.stackdepth = 1;
1231 curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
1233 pushd_directories = curdir;
1238 /*****************************************************************************
1241 * Pop a directory from the stack
1244 void WCMD_popd (void) {
1245 struct env_stack *temp = pushd_directories;
1247 if (!pushd_directories)
1250 /* pop the old environment from the stack, and make it the current dir */
1251 pushd_directories = temp->next;
1252 SetCurrentDirectoryW(temp->strings);
1253 LocalFree (temp->strings);
1257 /****************************************************************************
1260 * Batch file conditional.
1262 * On entry, cmdlist will point to command containing the IF, and optionally
1263 * the first command to execute (if brackets not found)
1264 * If &&'s were found, this may be followed by a record flagged as isAmpersand
1265 * If ('s were found, execute all within that bracket
1266 * Command may optionally be followed by an ELSE - need to skip instructions
1267 * in the else using the same logic
1269 * FIXME: Much more syntax checking needed!
1272 void WCMD_if (WCHAR *p, CMD_LIST **cmdList) {
1274 int negate = 0, test = 0;
1275 WCHAR condition[MAX_PATH], *command, *s;
1276 static const WCHAR notW[] = {'n','o','t','\0'};
1277 static const WCHAR errlvlW[] = {'e','r','r','o','r','l','e','v','e','l','\0'};
1278 static const WCHAR existW[] = {'e','x','i','s','t','\0'};
1279 static const WCHAR defdW[] = {'d','e','f','i','n','e','d','\0'};
1280 static const WCHAR eqeqW[] = {'=','=','\0'};
1281 static const WCHAR parmI[] = {'/','I','\0'};
1283 if (!lstrcmpiW (param1, notW)) {
1285 strcpyW (condition, param2);
1288 strcpyW (condition, param1);
1290 WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
1292 if (!lstrcmpiW (condition, errlvlW)) {
1293 if (errorlevel >= atoiW(WCMD_parameter (p, 1+negate, NULL))) test = 1;
1294 WCMD_parameter (p, 2+negate, &command);
1296 else if (!lstrcmpiW (condition, existW)) {
1297 if (GetFileAttributesW(WCMD_parameter (p, 1+negate, NULL)) != INVALID_FILE_ATTRIBUTES) {
1300 WCMD_parameter (p, 2+negate, &command);
1302 else if (!lstrcmpiW (condition, defdW)) {
1303 if (GetEnvironmentVariableW(WCMD_parameter (p, 1+negate, NULL), NULL, 0) > 0) {
1306 WCMD_parameter (p, 2+negate, &command);
1308 else if ((s = strstrW (p, eqeqW))) {
1310 if (strstrW (quals, parmI) == NULL) {
1311 if (!lstrcmpW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1314 if (!lstrcmpiW (condition, WCMD_parameter (s, 0, NULL))) test = 1;
1316 WCMD_parameter (s, 1, &command);
1319 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
1323 /* Process rest of IF statement which is on the same line
1324 Note: This may process all or some of the cmdList (eg a GOTO) */
1325 WCMD_part_execute(cmdList, command, NULL, NULL, TRUE, (test != negate));
1328 /****************************************************************************
1331 * Move a file, directory tree or wildcarded set of files.
1334 void WCMD_move (void) {
1337 WIN32_FIND_DATAW fd;
1339 WCHAR input[MAX_PATH];
1340 WCHAR output[MAX_PATH];
1342 WCHAR dir[MAX_PATH];
1343 WCHAR fname[MAX_PATH];
1344 WCHAR ext[MAX_PATH];
1346 if (param1[0] == 0x00) {
1347 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1351 /* If no destination supplied, assume current directory */
1352 if (param2[0] == 0x00) {
1353 strcpyW(param2, dotW);
1356 /* If 2nd parm is directory, then use original filename */
1357 /* Convert partial path to full path */
1358 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1359 GetFullPathNameW(param2, sizeof(output)/sizeof(WCHAR), output, NULL);
1360 WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1361 wine_dbgstr_w(param1), wine_dbgstr_w(output));
1363 /* Split into components */
1364 WCMD_splitpath(input, drive, dir, fname, ext);
1366 hff = FindFirstFileW(input, &fd);
1367 while (hff != INVALID_HANDLE_VALUE) {
1368 WCHAR dest[MAX_PATH];
1369 WCHAR src[MAX_PATH];
1372 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1374 /* Build src & dest name */
1375 strcpyW(src, drive);
1378 /* See if dest is an existing directory */
1379 attribs = GetFileAttributesW(output);
1380 if (attribs != INVALID_FILE_ATTRIBUTES &&
1381 (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
1382 strcpyW(dest, output);
1383 strcatW(dest, slashW);
1384 strcatW(dest, fd.cFileName);
1386 strcpyW(dest, output);
1389 strcatW(src, fd.cFileName);
1391 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1392 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1394 /* Check if file is read only, otherwise move it */
1395 attribs = GetFileAttributesW(src);
1396 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1397 (attribs & FILE_ATTRIBUTE_READONLY)) {
1398 SetLastError(ERROR_ACCESS_DENIED);
1403 /* If destination exists, prompt unless /Y supplied */
1404 if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
1406 WCHAR copycmd[MAXSTRING];
1409 /* /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
1410 if (strstrW (quals, parmNoY))
1412 else if (strstrW (quals, parmY))
1415 const WCHAR copyCmdW[] = {'C','O','P','Y','C','M','D','\0'};
1416 len = GetEnvironmentVariableW(copyCmdW, copycmd, sizeof(copycmd)/sizeof(WCHAR));
1417 force = (len && len < (sizeof(copycmd)/sizeof(WCHAR))
1418 && ! lstrcmpiW (copycmd, parmY));
1421 /* Prompt if overwriting */
1423 WCHAR question[MAXSTRING];
1426 strcpyW(yesChar, WCMD_LoadMessage(WCMD_YES));
1428 /* Ask for confirmation */
1429 wsprintfW(question, WCMD_LoadMessage(WCMD_OVERWRITE), dest);
1430 ok = WCMD_ask_confirm(question, FALSE, NULL);
1432 /* So delete the destination prior to the move */
1434 if (!DeleteFileW(dest)) {
1435 WCMD_print_error ();
1444 status = MoveFileW(src, dest);
1446 status = 1; /* Anything other than 0 to prevent error msg below */
1451 WCMD_print_error ();
1455 /* Step on to next match */
1456 if (FindNextFileW(hff, &fd) == 0) {
1458 hff = INVALID_HANDLE_VALUE;
1464 /****************************************************************************
1467 * Wait for keyboard input.
1470 void WCMD_pause (void) {
1475 WCMD_output (anykey);
1476 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
1477 sizeof(string)/sizeof(WCHAR), &count, NULL);
1480 /****************************************************************************
1483 * Delete a directory.
1486 void WCMD_remove_dir (WCHAR *command) {
1489 int argsProcessed = 0;
1490 WCHAR *argN = command;
1491 static const WCHAR parmS[] = {'/','S','\0'};
1492 static const WCHAR parmQ[] = {'/','Q','\0'};
1494 /* Loop through all args */
1496 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
1497 if (argN && argN[0] != '/') {
1498 WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
1499 wine_dbgstr_w(quals));
1502 /* If subdirectory search not supplied, just try to remove
1503 and report error if it fails (eg if it contains a file) */
1504 if (strstrW (quals, parmS) == NULL) {
1505 if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
1507 /* Otherwise use ShFileOp to recursively remove a directory */
1510 SHFILEOPSTRUCTW lpDir;
1513 if (strstrW (quals, parmQ) == NULL) {
1515 WCHAR question[MAXSTRING];
1516 static const WCHAR fmt[] = {'%','s',' ','\0'};
1518 /* Ask for confirmation */
1519 wsprintfW(question, fmt, thisArg);
1520 ok = WCMD_ask_confirm(question, TRUE, NULL);
1522 /* Abort if answer is 'N' */
1529 lpDir.pFrom = thisArg;
1530 lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
1531 lpDir.wFunc = FO_DELETE;
1532 if (SHFileOperationW(&lpDir)) WCMD_print_error ();
1537 /* Handle no valid args */
1538 if (argsProcessed == 0) {
1539 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1545 /****************************************************************************
1551 void WCMD_rename (void) {
1555 WIN32_FIND_DATAW fd;
1556 WCHAR input[MAX_PATH];
1557 WCHAR *dotDst = NULL;
1559 WCHAR dir[MAX_PATH];
1560 WCHAR fname[MAX_PATH];
1561 WCHAR ext[MAX_PATH];
1566 /* Must be at least two args */
1567 if (param1[0] == 0x00 || param2[0] == 0x00) {
1568 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
1573 /* Destination cannot contain a drive letter or directory separator */
1574 if ((strchrW(param1,':') != NULL) || (strchrW(param1,'\\') != NULL)) {
1575 SetLastError(ERROR_INVALID_PARAMETER);
1581 /* Convert partial path to full path */
1582 GetFullPathNameW(param1, sizeof(input)/sizeof(WCHAR), input, NULL);
1583 WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
1584 wine_dbgstr_w(param1), wine_dbgstr_w(param2));
1585 dotDst = strchrW(param2, '.');
1587 /* Split into components */
1588 WCMD_splitpath(input, drive, dir, fname, ext);
1590 hff = FindFirstFileW(input, &fd);
1591 while (hff != INVALID_HANDLE_VALUE) {
1592 WCHAR dest[MAX_PATH];
1593 WCHAR src[MAX_PATH];
1594 WCHAR *dotSrc = NULL;
1597 WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
1599 /* FIXME: If dest name or extension is *, replace with filename/ext
1600 part otherwise use supplied name. This supports:
1602 ren jim.* fred.* etc
1603 However, windows has a more complex algorithm supporting eg
1604 ?'s and *'s mid name */
1605 dotSrc = strchrW(fd.cFileName, '.');
1607 /* Build src & dest name */
1608 strcpyW(src, drive);
1611 dirLen = strlenW(src);
1612 strcatW(src, fd.cFileName);
1615 if (param2[0] == '*') {
1616 strcatW(dest, fd.cFileName);
1617 if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
1619 strcatW(dest, param2);
1620 if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
1623 /* Build Extension */
1624 if (dotDst && (*(dotDst+1)=='*')) {
1625 if (dotSrc) strcatW(dest, dotSrc);
1626 } else if (dotDst) {
1627 if (dotDst) strcatW(dest, dotDst);
1630 WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
1631 WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
1633 /* Check if file is read only, otherwise move it */
1634 attribs = GetFileAttributesW(src);
1635 if ((attribs != INVALID_FILE_ATTRIBUTES) &&
1636 (attribs & FILE_ATTRIBUTE_READONLY)) {
1637 SetLastError(ERROR_ACCESS_DENIED);
1640 status = MoveFileW(src, dest);
1644 WCMD_print_error ();
1648 /* Step on to next match */
1649 if (FindNextFileW(hff, &fd) == 0) {
1651 hff = INVALID_HANDLE_VALUE;
1657 /*****************************************************************************
1660 * Make a copy of the environment.
1662 static WCHAR *WCMD_dupenv( const WCHAR *env )
1672 len += (strlenW(&env[len]) + 1);
1674 env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
1677 WINE_ERR("out of memory\n");
1680 memcpy (env_copy, env, len*sizeof (WCHAR));
1686 /*****************************************************************************
1689 * setlocal pushes the environment onto a stack
1690 * Save the environment as unicode so we don't screw anything up.
1692 void WCMD_setlocal (const WCHAR *s) {
1694 struct env_stack *env_copy;
1695 WCHAR cwd[MAX_PATH];
1697 /* DISABLEEXTENSIONS ignored */
1699 env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
1702 WINE_ERR ("out of memory\n");
1706 env = GetEnvironmentStringsW ();
1708 env_copy->strings = WCMD_dupenv (env);
1709 if (env_copy->strings)
1711 env_copy->next = saved_environment;
1712 saved_environment = env_copy;
1714 /* Save the current drive letter */
1715 GetCurrentDirectoryW(MAX_PATH, cwd);
1716 env_copy->u.cwd = cwd[0];
1719 LocalFree (env_copy);
1721 FreeEnvironmentStringsW (env);
1725 /*****************************************************************************
1728 * endlocal pops the environment off a stack
1729 * Note: When searching for '=', search from WCHAR position 1, to handle
1730 * special internal environment variables =C:, =D: etc
1732 void WCMD_endlocal (void) {
1733 WCHAR *env, *old, *p;
1734 struct env_stack *temp;
1737 if (!saved_environment)
1740 /* pop the old environment from the stack */
1741 temp = saved_environment;
1742 saved_environment = temp->next;
1744 /* delete the current environment, totally */
1745 env = GetEnvironmentStringsW ();
1746 old = WCMD_dupenv (GetEnvironmentStringsW ());
1749 n = strlenW(&old[len]) + 1;
1750 p = strchrW(&old[len] + 1, '=');
1754 SetEnvironmentVariableW (&old[len], NULL);
1759 FreeEnvironmentStringsW (env);
1761 /* restore old environment */
1762 env = temp->strings;
1765 n = strlenW(&env[len]) + 1;
1766 p = strchrW(&env[len] + 1, '=');
1770 SetEnvironmentVariableW (&env[len], p);
1775 /* Restore current drive letter */
1776 if (IsCharAlphaW(temp->u.cwd)) {
1778 WCHAR cwd[MAX_PATH];
1779 static const WCHAR fmt[] = {'=','%','c',':','\0'};
1781 wsprintfW(envvar, fmt, temp->u.cwd);
1782 if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
1783 WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
1784 SetCurrentDirectoryW(cwd);
1792 /*****************************************************************************
1793 * WCMD_setshow_attrib
1795 * Display and optionally sets DOS attributes on a file or directory
1799 void WCMD_setshow_attrib (void) {
1803 WIN32_FIND_DATAW fd;
1804 WCHAR flags[9] = {' ',' ',' ',' ',' ',' ',' ',' ','\0'};
1805 WCHAR *name = param1;
1807 DWORD attrib_clear=0;
1809 if (param1[0] == '+' || param1[0] == '-') {
1811 /* FIXME: the real cmd can handle many more than two args; this should be in a loop */
1812 switch (param1[1]) {
1813 case 'H': case 'h': attrib |= FILE_ATTRIBUTE_HIDDEN; break;
1814 case 'S': case 's': attrib |= FILE_ATTRIBUTE_SYSTEM; break;
1815 case 'R': case 'r': attrib |= FILE_ATTRIBUTE_READONLY; break;
1816 case 'A': case 'a': attrib |= FILE_ATTRIBUTE_ARCHIVE; break;
1818 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
1821 switch (param1[0]) {
1822 case '+': attrib_set = attrib; break;
1823 case '-': attrib_clear = attrib; break;
1828 if (strlenW(name) == 0) {
1829 static const WCHAR slashStarW[] = {'\\','*','\0'};
1831 GetCurrentDirectoryW(sizeof(param2)/sizeof(WCHAR), name);
1832 strcatW (name, slashStarW);
1835 hff = FindFirstFileW(name, &fd);
1836 if (hff == INVALID_HANDLE_VALUE) {
1837 WCMD_output (WCMD_LoadMessage(WCMD_FILENOTFOUND), name);
1841 if (attrib_set || attrib_clear) {
1842 fd.dwFileAttributes &= ~attrib_clear;
1843 fd.dwFileAttributes |= attrib_set;
1844 if (!fd.dwFileAttributes)
1845 fd.dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
1846 SetFileAttributesW(name, fd.dwFileAttributes);
1848 static const WCHAR fmt[] = {'%','s',' ',' ',' ','%','s','\n','\0'};
1849 if (fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
1852 if (fd.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) {
1855 if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
1858 if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
1861 if (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
1864 if (fd.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) {
1867 WCMD_output (fmt, flags, fd.cFileName);
1868 for (count=0; count < 8; count++) flags[count] = ' ';
1870 } while (FindNextFileW(hff, &fd) != 0);
1875 /*****************************************************************************
1876 * WCMD_setshow_default
1878 * Set/Show the current default directory
1881 void WCMD_setshow_default (WCHAR *command) {
1887 WIN32_FIND_DATAW fd;
1889 static const WCHAR parmD[] = {'/','D','\0'};
1891 WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(command));
1893 /* Skip /D and trailing whitespace if on the front of the command line */
1894 if (CompareStringW(LOCALE_USER_DEFAULT,
1895 NORM_IGNORECASE | SORT_STRINGSORT,
1896 command, 2, parmD, -1) == 2) {
1898 while (*command && *command==' ') command++;
1901 GetCurrentDirectoryW(sizeof(cwd)/sizeof(WCHAR), cwd);
1902 if (strlenW(command) == 0) {
1903 strcatW (cwd, newline);
1907 /* Remove any double quotes, which may be in the
1908 middle, eg. cd "C:\Program Files"\Microsoft is ok */
1911 if (*command != '"') *pos++ = *command;
1916 /* Search for appropriate directory */
1917 WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
1918 hff = FindFirstFileW(string, &fd);
1919 while (hff != INVALID_HANDLE_VALUE) {
1920 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1921 WCHAR fpath[MAX_PATH];
1923 WCHAR dir[MAX_PATH];
1924 WCHAR fname[MAX_PATH];
1925 WCHAR ext[MAX_PATH];
1926 static const WCHAR fmt[] = {'%','s','%','s','%','s','\0'};
1928 /* Convert path into actual directory spec */
1929 GetFullPathNameW(string, sizeof(fpath)/sizeof(WCHAR), fpath, NULL);
1930 WCMD_splitpath(fpath, drive, dir, fname, ext);
1933 wsprintfW(string, fmt, drive, dir, fd.cFileName);
1936 hff = INVALID_HANDLE_VALUE;
1940 /* Step on to next match */
1941 if (FindNextFileW(hff, &fd) == 0) {
1943 hff = INVALID_HANDLE_VALUE;
1948 /* Change to that directory */
1949 WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
1951 status = SetCurrentDirectoryW(string);
1954 WCMD_print_error ();
1958 /* Save away the actual new directory, to store as current location */
1959 GetCurrentDirectoryW (sizeof(string)/sizeof(WCHAR), string);
1961 /* Restore old directory if drive letter would change, and
1962 CD x:\directory /D (or pushd c:\directory) not supplied */
1963 if ((strstrW(quals, parmD) == NULL) &&
1964 (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
1965 SetCurrentDirectoryW(cwd);
1969 /* Set special =C: type environment variable, for drive letter of
1970 change of directory, even if path was restored due to missing
1971 /D (allows changing drive letter when not resident on that
1973 if ((string[1] == ':') && IsCharAlphaW(string[0])) {
1975 strcpyW(env, equalW);
1976 memcpy(env+1, string, 2 * sizeof(WCHAR));
1978 WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
1979 SetEnvironmentVariableW(env, string);
1986 /****************************************************************************
1989 * Set/Show the system date
1990 * FIXME: Can't change date yet
1993 void WCMD_setshow_date (void) {
1995 WCHAR curdate[64], buffer[64];
1997 static const WCHAR parmT[] = {'/','T','\0'};
1999 if (strlenW(param1) == 0) {
2000 if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL,
2001 curdate, sizeof(curdate)/sizeof(WCHAR))) {
2002 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
2003 if (strstrW (quals, parmT) == NULL) {
2004 WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
2005 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE),
2006 buffer, sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2008 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2012 else WCMD_print_error ();
2015 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2019 /****************************************************************************
2022 static int WCMD_compare( const void *a, const void *b )
2025 const WCHAR * const *str_a = a, * const *str_b = b;
2026 r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
2027 *str_a, -1, *str_b, -1 );
2028 if( r == CSTR_LESS_THAN ) return -1;
2029 if( r == CSTR_GREATER_THAN ) return 1;
2033 /****************************************************************************
2034 * WCMD_setshow_sortenv
2036 * sort variables into order for display
2037 * Optionally only display those who start with a stub
2038 * returns the count displayed
2040 static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
2042 UINT count=0, len=0, i, displayedcount=0, stublen=0;
2045 if (stub) stublen = strlenW(stub);
2047 /* count the number of strings, and the total length */
2049 len += (strlenW(&s[len]) + 1);
2053 /* add the strings to an array */
2054 str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
2058 for( i=1; i<count; i++ )
2059 str[i] = str[i-1] + strlenW(str[i-1]) + 1;
2061 /* sort the array */
2062 qsort( str, count, sizeof (WCHAR*), WCMD_compare );
2065 for( i=0; i<count; i++ ) {
2066 if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
2067 NORM_IGNORECASE | SORT_STRINGSORT,
2068 str[i], stublen, stub, -1) == 2) {
2069 /* Don't display special internal variables */
2070 if (str[i][0] != '=') {
2071 WCMD_output_asis(str[i]);
2072 WCMD_output_asis(newline);
2079 return displayedcount;
2082 /****************************************************************************
2085 * Set/Show the environment variables
2088 void WCMD_setshow_env (WCHAR *s) {
2093 static const WCHAR parmP[] = {'/','P','\0'};
2096 if (param1[0] == 0x00 && quals[0] == 0x00) {
2097 env = GetEnvironmentStringsW();
2098 WCMD_setshow_sortenv( env, NULL );
2102 /* See if /P supplied, and if so echo the prompt, and read in a reply */
2103 if (CompareStringW(LOCALE_USER_DEFAULT,
2104 NORM_IGNORECASE | SORT_STRINGSORT,
2105 s, 2, parmP, -1) == 2) {
2106 WCHAR string[MAXSTRING];
2110 while (*s && *s==' ') s++;
2112 WCMD_opt_s_strip_quotes(s);
2114 /* If no parameter, or no '=' sign, return an error */
2115 if (!(*s) || ((p = strchrW (s, '=')) == NULL )) {
2116 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2120 /* Output the prompt */
2122 if (strlenW(p) != 0) WCMD_output(p);
2124 /* Read the reply */
2125 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2126 sizeof(string)/sizeof(WCHAR), &count, NULL);
2128 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2129 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2130 WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
2131 wine_dbgstr_w(string));
2132 status = SetEnvironmentVariableW(s, string);
2139 WCMD_opt_s_strip_quotes(s);
2140 p = strchrW (s, '=');
2142 env = GetEnvironmentStringsW();
2143 if (WCMD_setshow_sortenv( env, s ) == 0) {
2144 WCMD_output (WCMD_LoadMessage(WCMD_MISSINGENV), s);
2151 if (strlenW(p) == 0) p = NULL;
2152 status = SetEnvironmentVariableW(s, p);
2153 gle = GetLastError();
2154 if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
2156 } else if ((!status)) WCMD_print_error();
2160 /****************************************************************************
2163 * Set/Show the path environment variable
2166 void WCMD_setshow_path (WCHAR *command) {
2170 static const WCHAR pathW[] = {'P','A','T','H','\0'};
2171 static const WCHAR pathEqW[] = {'P','A','T','H','=','\0'};
2173 if (strlenW(param1) == 0) {
2174 status = GetEnvironmentVariableW(pathW, string, sizeof(string)/sizeof(WCHAR));
2176 WCMD_output_asis ( pathEqW);
2177 WCMD_output_asis ( string);
2178 WCMD_output_asis ( newline);
2181 WCMD_output (WCMD_LoadMessage(WCMD_NOPATH));
2185 if (*command == '=') command++; /* Skip leading '=' */
2186 status = SetEnvironmentVariableW(pathW, command);
2187 if (!status) WCMD_print_error();
2191 /****************************************************************************
2192 * WCMD_setshow_prompt
2194 * Set or show the command prompt.
2197 void WCMD_setshow_prompt (void) {
2200 static const WCHAR promptW[] = {'P','R','O','M','P','T','\0'};
2202 if (strlenW(param1) == 0) {
2203 SetEnvironmentVariableW(promptW, NULL);
2207 while ((*s == '=') || (*s == ' ')) s++;
2208 if (strlenW(s) == 0) {
2209 SetEnvironmentVariableW(promptW, NULL);
2211 else SetEnvironmentVariableW(promptW, s);
2215 /****************************************************************************
2218 * Set/Show the system time
2219 * FIXME: Can't change time yet
2222 void WCMD_setshow_time (void) {
2224 WCHAR curtime[64], buffer[64];
2227 static const WCHAR parmT[] = {'/','T','\0'};
2229 if (strlenW(param1) == 0) {
2231 if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL,
2232 curtime, sizeof(curtime)/sizeof(WCHAR))) {
2233 WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
2234 if (strstrW (quals, parmT) == NULL) {
2235 WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
2236 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2237 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2239 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2243 else WCMD_print_error ();
2246 WCMD_output (WCMD_LoadMessage(WCMD_NYI));
2250 /****************************************************************************
2253 * Shift batch parameters.
2254 * Optional /n says where to start shifting (n=0-8)
2257 void WCMD_shift (WCHAR *command) {
2260 if (context != NULL) {
2261 WCHAR *pos = strchrW(command, '/');
2266 } else if (*(pos+1)>='0' && *(pos+1)<='8') {
2267 start = (*(pos+1) - '0');
2269 SetLastError(ERROR_INVALID_PARAMETER);
2274 WINE_TRACE("Shifting variables, starting at %d\n", start);
2275 for (i=start;i<=8;i++) {
2276 context -> shift_count[i] = context -> shift_count[i+1] + 1;
2278 context -> shift_count[9] = context -> shift_count[9] + 1;
2283 /****************************************************************************
2286 * Set the console title
2288 void WCMD_title (WCHAR *command) {
2289 SetConsoleTitleW(command);
2292 /****************************************************************************
2295 * Copy a file to standard output.
2298 void WCMD_type (WCHAR *command) {
2301 WCHAR *argN = command;
2302 BOOL writeHeaders = FALSE;
2304 if (param1[0] == 0x00) {
2305 WCMD_output (WCMD_LoadMessage(WCMD_NOARG));
2309 if (param2[0] != 0x00) writeHeaders = TRUE;
2311 /* Loop through all args */
2314 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2322 WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2323 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2324 FILE_ATTRIBUTE_NORMAL, NULL);
2325 if (h == INVALID_HANDLE_VALUE) {
2326 WCMD_print_error ();
2327 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2331 static const WCHAR fmt[] = {'\n','%','s','\n','\n','\0'};
2332 WCMD_output(fmt, thisArg);
2334 while (WCMD_ReadFile (h, buffer, sizeof(buffer)/sizeof(WCHAR) - 1, &count, NULL)) {
2335 if (count == 0) break; /* ReadFile reports success on EOF! */
2337 WCMD_output_asis (buffer);
2341 WCMD_output_asis (newline);
2346 /****************************************************************************
2349 * Output either a file or stdin to screen in pages
2352 void WCMD_more (WCHAR *command) {
2355 WCHAR *argN = command;
2357 WCHAR moreStrPage[100];
2360 static const WCHAR moreStart[] = {'-','-',' ','\0'};
2361 static const WCHAR moreFmt[] = {'%','s',' ','-','-','\n','\0'};
2362 static const WCHAR moreFmt2[] = {'%','s',' ','(','%','2','.','2','d','%','%',
2363 ')',' ','-','-','\n','\0'};
2364 static const WCHAR conInW[] = {'C','O','N','I','N','$','\0'};
2366 /* Prefix the NLS more with '-- ', then load the text */
2368 strcpyW(moreStr, moreStart);
2369 LoadStringW(hinst, WCMD_MORESTR, &moreStr[3],
2370 (sizeof(moreStr)/sizeof(WCHAR))-3);
2372 if (param1[0] == 0x00) {
2374 /* Wine implements pipes via temporary files, and hence stdin is
2375 effectively reading from the file. This means the prompts for
2376 more are satisfied by the next line from the input (file). To
2377 avoid this, ensure stdin is to the console */
2378 HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
2379 HANDLE hConIn = CreateFileW(conInW, GENERIC_READ | GENERIC_WRITE,
2380 FILE_SHARE_READ, NULL, OPEN_EXISTING,
2381 FILE_ATTRIBUTE_NORMAL, 0);
2382 WINE_TRACE("No parms - working probably in pipe mode\n");
2383 SetStdHandle(STD_INPUT_HANDLE, hConIn);
2385 /* Warning: No easy way of ending the stream (ctrl+z on windows) so
2386 once you get in this bit unless due to a pipe, its going to end badly... */
2387 wsprintfW(moreStrPage, moreFmt, moreStr);
2389 WCMD_enter_paged_mode(moreStrPage);
2390 while (WCMD_ReadFile (hstdin, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2391 if (count == 0) break; /* ReadFile reports success on EOF! */
2393 WCMD_output_asis (buffer);
2395 WCMD_leave_paged_mode();
2397 /* Restore stdin to what it was */
2398 SetStdHandle(STD_INPUT_HANDLE, hstdin);
2399 CloseHandle(hConIn);
2403 BOOL needsPause = FALSE;
2405 /* Loop through all args */
2406 WINE_TRACE("Parms supplied - working through each file\n");
2407 WCMD_enter_paged_mode(moreStrPage);
2410 WCHAR *thisArg = WCMD_parameter (command, argno++, &argN);
2418 wsprintfW(moreStrPage, moreFmt2, moreStr, 100);
2419 WCMD_leave_paged_mode();
2420 WCMD_output_asis(moreStrPage);
2421 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), buffer,
2422 sizeof(buffer)/sizeof(WCHAR), &count, NULL);
2423 WCMD_enter_paged_mode(moreStrPage);
2427 WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
2428 h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
2429 FILE_ATTRIBUTE_NORMAL, NULL);
2430 if (h == INVALID_HANDLE_VALUE) {
2431 WCMD_print_error ();
2432 WCMD_output (WCMD_LoadMessage(WCMD_READFAIL), thisArg);
2436 ULONG64 fileLen = 0;
2437 WIN32_FILE_ATTRIBUTE_DATA fileInfo;
2439 /* Get the file size */
2440 GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
2441 fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
2444 while (WCMD_ReadFile (h, buffer, (sizeof(buffer)/sizeof(WCHAR))-1, &count, NULL)) {
2445 if (count == 0) break; /* ReadFile reports success on EOF! */
2449 /* Update % count (would be used in WCMD_output_asis as prompt) */
2450 wsprintfW(moreStrPage, moreFmt2, moreStr, (int) min(99, (curPos * 100)/fileLen));
2452 WCMD_output_asis (buffer);
2458 WCMD_leave_paged_mode();
2462 /****************************************************************************
2465 * Display verify flag.
2466 * FIXME: We don't actually do anything with the verify flag other than toggle
2470 void WCMD_verify (WCHAR *command) {
2474 count = strlenW(command);
2476 if (verify_mode) WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), onW);
2477 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), offW);
2480 if (lstrcmpiW(command, onW) == 0) {
2484 else if (lstrcmpiW(command, offW) == 0) {
2488 else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYERR));
2491 /****************************************************************************
2494 * Display version info.
2497 void WCMD_version (void) {
2499 WCMD_output (version_string);
2503 /****************************************************************************
2506 * Display volume info and/or set volume label. Returns 0 if error.
2509 int WCMD_volume (int mode, WCHAR *path) {
2511 DWORD count, serial;
2512 WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
2515 if (strlenW(path) == 0) {
2516 status = GetCurrentDirectoryW(sizeof(curdir)/sizeof(WCHAR), curdir);
2518 WCMD_print_error ();
2521 status = GetVolumeInformationW(NULL, label, sizeof(label)/sizeof(WCHAR),
2522 &serial, NULL, NULL, NULL, 0);
2525 static const WCHAR fmt[] = {'%','s','\\','\0'};
2526 if ((path[1] != ':') || (strlenW(path) != 2)) {
2527 WCMD_output (WCMD_LoadMessage(WCMD_SYNTAXERR));
2530 wsprintfW (curdir, fmt, path);
2531 status = GetVolumeInformationW(curdir, label, sizeof(label)/sizeof(WCHAR),
2536 WCMD_print_error ();
2539 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEDETAIL),
2540 curdir[0], label, HIWORD(serial), LOWORD(serial));
2542 WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
2543 WCMD_ReadFile (GetStdHandle(STD_INPUT_HANDLE), string,
2544 sizeof(string)/sizeof(WCHAR), &count, NULL);
2546 string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
2547 if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
2549 if (strlenW(path) != 0) {
2550 if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
2553 if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
2559 /**************************************************************************
2562 * Exit either the process, or just this batch program
2566 void WCMD_exit (CMD_LIST **cmdList) {
2568 static const WCHAR parmB[] = {'/','B','\0'};
2569 int rc = atoiW(param1); /* Note: atoi of empty parameter is 0 */
2571 if (context && lstrcmpiW(quals, parmB) == 0) {
2573 context -> skip_rest = TRUE;
2581 /*****************************************************************************
2584 * Lists or sets file associations (assoc = TRUE)
2585 * Lists or sets file types (assoc = FALSE)
2587 void WCMD_assoc (WCHAR *command, BOOL assoc) {
2590 DWORD accessOptions = KEY_READ;
2592 LONG rc = ERROR_SUCCESS;
2593 WCHAR keyValue[MAXSTRING];
2594 DWORD valueLen = MAXSTRING;
2596 static const WCHAR shOpCmdW[] = {'\\','S','h','e','l','l','\\',
2597 'O','p','e','n','\\','C','o','m','m','a','n','d','\0'};
2599 /* See if parameter includes '=' */
2601 newValue = strchrW(command, '=');
2602 if (newValue) accessOptions |= KEY_WRITE;
2604 /* Open a key to HKEY_CLASSES_ROOT for enumerating */
2605 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, nullW, 0,
2606 accessOptions, &key) != ERROR_SUCCESS) {
2607 WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
2611 /* If no parameters then list all associations */
2612 if (*command == 0x00) {
2615 /* Enumerate all the keys */
2616 while (rc != ERROR_NO_MORE_ITEMS) {
2617 WCHAR keyName[MAXSTRING];
2620 /* Find the next value */
2621 nameLen = MAXSTRING;
2622 rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
2624 if (rc == ERROR_SUCCESS) {
2626 /* Only interested in extension ones if assoc, or others
2628 if ((keyName[0] == '.' && assoc) ||
2629 (!(keyName[0] == '.') && (!assoc)))
2631 WCHAR subkey[MAXSTRING];
2632 strcpyW(subkey, keyName);
2633 if (!assoc) strcatW(subkey, shOpCmdW);
2635 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2637 valueLen = sizeof(keyValue)/sizeof(WCHAR);
2638 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2639 WCMD_output_asis(keyName);
2640 WCMD_output_asis(equalW);
2641 /* If no default value found, leave line empty after '=' */
2642 if (rc == ERROR_SUCCESS) {
2643 WCMD_output_asis(keyValue);
2645 WCMD_output_asis(newline);
2646 RegCloseKey(readKey);
2654 /* Parameter supplied - if no '=' on command line, its a query */
2655 if (newValue == NULL) {
2657 WCHAR subkey[MAXSTRING];
2659 /* Query terminates the parameter at the first space */
2660 strcpyW(keyValue, command);
2661 space = strchrW(keyValue, ' ');
2662 if (space) *space=0x00;
2664 /* Set up key name */
2665 strcpyW(subkey, keyValue);
2666 if (!assoc) strcatW(subkey, shOpCmdW);
2668 if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
2670 rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
2671 WCMD_output_asis(command);
2672 WCMD_output_asis(equalW);
2673 /* If no default value found, leave line empty after '=' */
2674 if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
2675 WCMD_output_asis(newline);
2676 RegCloseKey(readKey);
2679 WCHAR msgbuffer[MAXSTRING];
2680 WCHAR outbuffer[MAXSTRING];
2682 /* Load the translated 'File association not found' */
2684 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2686 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, sizeof(msgbuffer)/sizeof(WCHAR));
2688 wsprintfW(outbuffer, msgbuffer, keyValue);
2689 WCMD_output_asis(outbuffer);
2693 /* Not a query - its a set or clear of a value */
2696 WCHAR subkey[MAXSTRING];
2698 /* Get pointer to new value */
2702 /* Set up key name */
2703 strcpyW(subkey, command);
2704 if (!assoc) strcatW(subkey, shOpCmdW);
2706 /* If nothing after '=' then clear value - only valid for ASSOC */
2707 if (*newValue == 0x00) {
2709 if (assoc) rc = RegDeleteKeyW(key, command);
2710 if (assoc && rc == ERROR_SUCCESS) {
2711 WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(command));
2713 } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
2718 WCHAR msgbuffer[MAXSTRING];
2719 WCHAR outbuffer[MAXSTRING];
2721 /* Load the translated 'File association not found' */
2723 LoadStringW(hinst, WCMD_NOASSOC, msgbuffer,
2724 sizeof(msgbuffer)/sizeof(WCHAR));
2726 LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer,
2727 sizeof(msgbuffer)/sizeof(WCHAR));
2729 wsprintfW(outbuffer, msgbuffer, keyValue);
2730 WCMD_output_asis(outbuffer);
2734 /* It really is a set value = contents */
2736 rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
2737 accessOptions, NULL, &readKey, NULL);
2738 if (rc == ERROR_SUCCESS) {
2739 rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
2740 (LPBYTE)newValue, strlenW(newValue));
2741 RegCloseKey(readKey);
2744 if (rc != ERROR_SUCCESS) {
2748 WCMD_output_asis(command);
2749 WCMD_output_asis(equalW);
2750 WCMD_output_asis(newValue);
2751 WCMD_output_asis(newline);
2761 /****************************************************************************
2764 * Clear the terminal screen.
2767 void WCMD_color (void) {
2769 /* Emulate by filling the screen from the top left to bottom right with
2770 spaces, then moving the cursor to the top left afterwards */
2771 CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
2772 HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
2774 if (param1[0] != 0x00 && strlenW(param1) > 2) {
2775 WCMD_output (WCMD_LoadMessage(WCMD_ARGERR));
2779 if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
2785 screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
2790 /* Convert the color hex digits */
2791 if (param1[0] == 0x00) {
2792 color = defaultColor;
2794 color = strtoulW(param1, NULL, 16);
2797 /* Fail if fg == bg color */
2798 if (((color & 0xF0) >> 4) == (color & 0x0F)) {
2803 /* Set the current screen contents and ensure all future writes
2804 remain this color */
2805 FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
2806 SetConsoleTextAttribute(hStdOut, color);